跳到正文
← 入门指南

Hermes Agent 核心功能

18 章 · 约 215 千字

官方文档「核心功能」:工具、技能、记忆、定时、委派、插件、MCP 等。

目录

功能概览

Hermes Agent 包含一套丰富的能力,远超基础聊天范畴。从持久化记忆、文件感知上下文,到浏览器自动化和语音对话,这些功能协同工作,使 Hermes 成为一个强大的自主助手。

核心功能​

  • 工具与工具集 — 工具是扩展 Agent 能力的函数。它们被组织成逻辑工具集,可按平台启用或禁用,涵盖网络搜索、终端执行、文件编辑、记忆、委派等功能。
  • 技能系统 — Agent 可按需加载的知识文档。技能遵循渐进式披露模式以最小化 token 用量,并兼容 agentskills.io 开放标准。
  • 持久化记忆 — 跨会话持久保存的有界、精选记忆。Hermes 通过 MEMORY.md 和 USER.md 记住你的偏好、项目、环境及已学习的内容。
  • 上下文文件 — Hermes 自动发现并加载项目上下文文件(.hermes.md、AGENTS.md、CLAUDE.md、SOUL.md、.cursorrules),这些文件决定了它在你项目中的行为方式。
  • 上下文引用 — 输入 @ 后跟引用内容,可将文件、文件夹、git diff 和 URL 直接注入消息中。Hermes 会内联展开引用并自动附加相应内容。
  • 检查点 — Hermes 在进行文件更改前自动为工作目录创建快照,提供安全网,可通过 /rollback 回滚至出错前的状态。

自动化​

  • 定时任务(Cron) — 使用自然语言或 cron 表达式调度自动运行的任务。任务可附加技能、将结果推送至任意平台,并支持暂停/恢复/编辑操作。
  • 子 Agent 委派 — delegate_task 工具可生成具有独立上下文、受限工具集和独立终端会话的子 Agent 实例。默认并发运行 3 个子 Agent(可配置),支持并行工作流。
  • 代码执行 — execute_code 工具允许 Agent 编写以编程方式调用 Hermes 工具的 Python 脚本,通过沙箱 RPC 执行将多步骤工作流压缩为单次 LLM 调用。
  • 事件 Hook — 在关键生命周期节点运行自定义代码。Gateway hook 处理日志、告警和 webhook;plugin hook 处理工具拦截、指标和护栏。
  • 批处理 — 跨数百或数千个 prompt(提示词)并行运行 Hermes Agent,生成 ShareGPT 格式的结构化轨迹数据,用于训练数据生成或评估。

媒体与网络​

  • 语音模式 — 跨 CLI 和消息平台的完整语音交互。使用麦克风与 Agent 对话,收听语音回复,并在 Discord 语音频道中进行实时语音对话。
  • 浏览器自动化 — 支持多种后端的完整浏览器自动化:Browserbase 云端、Browser Use 云端、通过 CDP 连接的本地 Chrome/Brave/Chromium/Edge,或本地 Chromium。可导航网站、填写表单并提取信息。
  • 视觉与图片粘贴 — 多模态视觉支持。将剪贴板中的图片粘贴到 CLI,并使用任意支持视觉的模型请求 Agent 分析、描述或处理图片。
  • 图像生成 — 使用 FAL.ai 从文本 prompt 生成图像。支持九种模型(FLUX 2 Klein/Pro、GPT-Image 1.5/2、Nano Banana Pro、Ideogram V3、Recraft V4 Pro、Qwen、Z-Image Turbo);可通过 hermes tools 选择。
  • 语音与 TTS — 跨所有消息平台的文字转语音输出和语音消息转录,提供十种原生提供商选项:Edge TTS(免费)、ElevenLabs、OpenAI TTS、MiniMax、Mistral Voxtral、Google Gemini、xAI、NeuTTS、KittenTTS 和 Piper——以及支持任意本地 TTS CLI 的自定义命令提供商。

集成​

  • MCP 集成 — 通过 stdio 或 HTTP 传输连接任意 MCP 服务器。无需编写原生 Hermes 工具,即可访问来自 GitHub、数据库、文件系统和内部 API 的外部工具。支持按服务器过滤工具及 sampling(采样)。
  • 提供商路由 — 对 AI 提供商处理请求的方式进行精细控制。通过排序、白名单、黑名单和优先级排序,在成本、速度或质量之间优化。
  • 备用提供商 — 当主模型遇到错误时自动故障转移至备用 LLM 提供商,包括针对视觉和压缩等辅助任务的独立备用机制。
  • 凭证池 — 在同一提供商的多个密钥之间分发 API 调用。在触发速率限制或发生故障时自动轮换。
  • Prompt 缓存 — 针对原生 Anthropic、OpenRouter 和 Nous Portal 上的 Claude,内置跨会话 1 小时前缀缓存。始终开启,无需配置。
  • 记忆提供商 — 接入外部记忆后端(Honcho、OpenViking、Mem0、Hindsight、Holographic、RetainDB、ByteRover、Supermemory),实现跨会话用户建模和超越内置记忆系统的个性化。
  • API 服务器 — 将 Hermes 作为兼容 OpenAI 的 HTTP 端点暴露。连接任何支持 OpenAI 格式的前端——Open WebUI、LobeChat、LibreChat 等。
  • IDE 集成(ACP) — 在兼容 ACP 的编辑器(如 VS Code、Zed 和 JetBrains)中使用 Hermes。聊天、工具活动、文件 diff 和终端命令均在编辑器内渲染。
  • 强化学习训练 — 从 Agent 会话中生成轨迹数据,用于强化学习和模型微调。

自定义​

  • 个性与 SOUL.md — 完全可自定义的 Agent 个性。SOUL.md 是主要身份文件——系统提示词中的第一项——你可以在每个会话中切换内置或自定义的 /personality 预设。
  • 皮肤与主题 — 自定义 CLI 的视觉呈现:横幅颜色、加载动画图标和动词、响应框标签、品牌文字,以及工具活动前缀。
  • 插件 — 无需修改核心代码即可添加自定义工具、hook 和集成。三种插件类型:通用插件(工具/hook)、记忆提供商(跨会话知识)和上下文引擎(替代上下文管理)。通过统一的 hermes plugins 交互式界面管理。

工具与工具集

本页的 Python 依赖命令使用 PM 准备的源码环境。

依赖变更后,请重新激活该 checkout 并重启 Hermes。

工具是扩展 Agent 能力的函数。它们被组织为逻辑上的工具集,可按平台启用或禁用。

可用工具​

Hermes 内置了丰富的工具注册表,涵盖网页搜索、浏览器自动化、终端执行、文件编辑、记忆、委托、RL 训练、消息投递等功能。插件还可以提供更多工具,例如 Home Assistant 设备控制由插件目录中的 homeassistant 插件提供。

备注

Honcho 跨会话记忆作为插件目录中的记忆提供者插件提供(hermes plugins install honcho),而非内置工具集。参见 Memory Providers。

高层分类:

| | 分类 | 示例 | 描述

| Web | web_search, web_extract | 搜索网页并提取页面内容。

| X 搜索 | x_search | 通过 xAI 内置的 x_search Responses 工具搜索 X(Twitter)帖子和话题——需要 xAI 凭据(SuperGrok OAuth 或 XAI_API_KEY);默认关闭,可通过 hermes tools → 🐦 X (Twitter) Search 启用。

| 终端与文件 | terminal, process, read_file, patch | 执行命令并操作文件。

| 浏览器 | browser_navigate, browser_snapshot, browser_vision | 支持文本和视觉的交互式浏览器自动化。

| 媒体 | vision_analyze, image_generate, video_generate, video_analyze, text_to_speech | 多模态分析与生成。video_generate 和 video_analyze 需手动启用(通过 hermes tools 或 --toolsets 添加 video_gen / video 工具集)。

| Agent 编排 | todo, clarify, execute_code, delegate_task | 规划、澄清、代码执行及子 Agent 委托。

| 记忆与召回 | memory, session_search | 持久化记忆与会话搜索。

| 自动化与投递 | cronjob, send_message | 支持创建/列出/更新/暂停/恢复/运行/删除操作的定时任务,以及出站消息投递。

| 集成 | MCP server 工具、插件工具 | MCP 及插件集成,例如 Home Assistant(ha_*,来自插件目录中的 homeassistant 插件)。

如需查看由代码派生的权威注册表,请参阅 内置工具参考 和 工具集参考。

Nous Tool Gateway

付费 Nous Portal 订阅者可通过 Tool Gateway 使用网页搜索、图像生成、TTS 和浏览器自动化——无需单独配置 API 密钥。运行 hermes model 启用,或通过 hermes tools 配置各工具。

使用工具集​

# 使用指定工具集
hermes chat --toolsets "web,terminal"

# 查看所有可用工具
hermes tools

# 按平台交互式配置工具
hermes tools

常用工具集包括 web、search、terminal、file、browser、vision、image_gen、moa、skills、tts、todo、memory、session_search、cronjob、code_execution、delegation、clarify、messaging、discord、discord_admin、debugging 和 safe。

完整列表(包括 hermes-cli、hermes-telegram 等平台预设以及 mcp-<server> 等动态 MCP 工具集)请参阅 工具集参考。

终端后端​

终端工具可在不同环境中执行命令:

| | 后端 | 描述 | 适用场景

| local | 在本机运行(默认) | 开发、可信任务

| docker | 隔离容器 | 安全性、可复现性

| ssh | 远程服务器 | 沙箱隔离,防止 Agent 修改自身代码

| singularity | HPC 容器 | 集群计算、无 root 权限

| modal | 云端执行 | 无服务器、弹性扩展

| daytona | 云端沙箱工作区 | 持久化远程开发环境

| vercel_sandbox | Vercel Sandbox 云微虚拟机 | 带快照文件系统持久化的云端执行

配置​

# 在 ~/.hermes/config.yaml 中
terminal:
  backend: local    # 或:docker, ssh, singularity, modal, daytona, vercel_sandbox
  cwd: "."          # 工作目录
  timeout: 180      # 命令超时时间(秒)

Docker 后端​

terminal:
  backend: docker
  docker_image: python:3.11-slim

单个持久容器,在整个进程生命周期内共享。 Hermes 在首次使用时启动一个长期运行的容器(docker run -d ... sleep 2h),并通过 docker exec 将所有终端、文件及 execute_code 调用路由到同一容器中。工作目录变更、已安装的包、环境调整以及写入 /workspace 的文件,在同一 Hermes 进程的整个生命周期内,跨 /new、/reset 和 delegate_task 子 Agent 均会保留。容器在关闭时停止并删除。

这意味着 Docker 后端的行为类似持久化沙箱虚拟机,而非每次命令都使用全新容器。如果你执行过一次 pip install foo,该包在本次会话的剩余时间内均可用。如果你执行了 cd /workspace/project,后续的 ls 调用将看到该目录。完整的生命周期详情及控制 /workspace 和 /root 是否跨 Hermes 重启保留的 container_persistent 标志,请参阅 配置 → Docker 后端。

SSH 后端​

推荐用于安全场景——Agent 无法修改自身代码:

terminal:
  backend: ssh
# 在 ~/.hermes/.env 中设置凭据
TERMINAL_SSH_HOST=my-server.example.com
TERMINAL_SSH_USER=myuser
TERMINAL_SSH_KEY=~/.ssh/id_rsa

Singularity/Apptainer​

# 为并行 worker 预构建 SIF
apptainer build ~/python.sif docker://python:3.11-slim

# 配置
hermes config set terminal.backend singularity
hermes config set terminal.singularity_image ~/python.sif

Modal(无服务器云)​

python -c "import pm; pm.sync_venv(['modal'], explicit=True)"
modal setup
hermes config set terminal.backend modal

Vercel Sandbox​

python -c "import pm; pm.sync_venv(['vercel'], explicit=True)"
hermes config set terminal.backend vercel_sandbox
hermes config set terminal.vercel_runtime node24

需同时配置 VERCEL_TOKEN、VERCEL_PROJECT_ID 和 VERCEL_TEAM_ID 三个凭据。此访问令牌配置方式是在 Render、Railway、Docker 及类似平台上进行部署和正常长期运行 Hermes 进程的推荐路径。支持的运行时为 node24、node22 和 python3.13;Hermes 默认使用 /vercel/sandbox 作为远程工作区根目录。

对于本地一次性开发,Hermes 也接受短期 Vercel OIDC token:

VERCEL_OIDC_TOKEN="$(vc project token <project-name>)" hermes chat

在已关联的 Vercel 项目目录中:

VERCEL_OIDC_TOKEN="$(vc project token)" hermes chat

启用 container_persistent: true 后,Hermes 使用 Vercel 快照在同一任务的沙箱重建时保留文件系统状态,其中可包含沙箱内 Hermes 同步的凭据、技能和缓存文件。快照不保留活跃进程、PID 空间或相同的活跃沙箱标识。

后台终端命令使用 Hermes 通用的非本地进程流程:在沙箱存活期间,spawn、poll、wait、log 和 kill 均通过标准 process 工具运行,但 Hermes 不提供清理或重启后的原生 Vercel 后台进程恢复能力。

container_disk 保持未设置或使用共享默认值 51200;Vercel Sandbox 不支持自定义磁盘大小,设置后将导致诊断/后端创建失败。

容器资源​

为所有容器后端配置 CPU、内存、磁盘和持久化:

terminal:
  backend: docker  # 或 singularity, modal, daytona, vercel_sandbox
  container_cpu: 1              # CPU 核心数(默认:1)
  container_memory: 5120        # 内存(MB,默认:5GB)
  container_disk: 51200         # 磁盘(MB,默认:50GB)
  container_persistent: true    # 跨会话持久化文件系统(默认:true)

启用 container_persistent: true 后,已安装的包、文件和配置将跨会话保留。

容器安全​

所有容器后端均启用安全加固:

  • 只读根文件系统(Docker)
  • 丢弃所有 Linux capabilities
  • 禁止权限提升
  • PID 限制(256 个进程)
  • 完整命名空间隔离
  • 通过卷挂载实现持久化工作区,而非可写根层

Docker 可通过 terminal.docker_forward_env 接受显式的环境变量白名单,但转发的变量对容器内的命令可见,应视为在该会话中已暴露。

后台进程管理​

启动后台进程并进行管理:

terminal(command="pytest -v tests/", background=true)
# 返回:{"session_id": "proc_abc123", "pid": 12345}

# 然后使用 process 工具进行管理:
process(action="list")       # 显示所有运行中的进程
process(action="poll", session_id="proc_abc123")   # 检查状态
process(action="wait", session_id="proc_abc123")   # 阻塞直到完成
process(action="log", session_id="proc_abc123")    # 完整输出
process(action="kill", session_id="proc_abc123")   # 终止进程
process(action="write", session_id="proc_abc123", data="y")  # 发送输入

PTY 模式(pty=true)可启用 Codex 和 Claude Code 等交互式 CLI 工具。

已完成后台进程的结果​

后台命令完成后,其退出状态和捕获的输出会保留在当前配置档案中。

无头父进程退出或 Hermes 重启后,请恢复启动该命令的会话(或其上下文压缩后的

延续会话),再使用原始 session_id 调用 process(action="log") 读取输出、

调用 process(action="poll") 查看退出状态。即使知道完整进程标识,其他会话

或未绑定所属会话的请求也不能读取保留的结果。process(action="list")

也会列出当前任务或会话的保留结果。

每个配置档案在 logs/process-results/ 下最多保留最近 64 个已完成结果,

自完成起保存不超过 7 天。每份记录最多包含滚动输出末尾的 200,000 个字符,

始终使用终端的敏感信息脱敏规则,即使实时输出的脱敏功能已关闭。

后续读取或写入结果时会清理过期记录。

恢复结果不会重新运行命令,也不会重放完成通知。这仅保护父进程存活期间已经

完成的工作,不保证未完成的子进程在超时或崩溃后继续运行。

Sudo 支持​

如果命令需要 sudo,系统会提示你输入密码(在本次会话内缓存)。也可在 ~/.hermes/.env 中设置 SUDO_PASSWORD。

注意

在消息平台上,如果 sudo 失败,输出中会提示将 SUDO_PASSWORD 添加到 ~/.hermes/.env。

Skills 系统

Skills 是 agent 在需要时可以加载的按需知识文档。它们遵循渐进式披露(progressive disclosure)模式以最小化 token 用量,并兼容 agentskills.io 开放标准。

所有 skills 存放在 ~/.hermes/skills/ 中——这是主目录和唯一可信来源。全新安装时,捆绑的 skills 会从仓库复制过来。通过 Hub 安装和 agent 创建的 skills 也存放在此处。agent 可以修改或删除任何 skill。

你也可以让 Hermes 指向外部 skill 目录——与本地目录一起扫描的额外文件夹。参见下方的外部 Skill 目录。

另请参阅:

  • 捆绑 Skills 目录
  • 官方可选 Skills 目录

使用 Skills​

每个已安装的 skill 都会自动作为斜杠命令可用:

# 在 CLI 或任何消息平台中:
/gif-search funny cats
/axolotl help me fine-tune Llama 3 on my dataset
/github-pr-workflow create a PR for the auth refactor
/songsee analyze the frequency spread of this mix

# 只输入 skill 名称即可加载它,并让 agent 询问你的需求:
/excalidraw

(计划模式的工作方式相同,但现在是内置命令:/plan [request] 告知 Hermes 在需要时检查上下文、编写 markdown 实现计划而非直接执行任务,并将结果保存在相对于当前工作区/后端工作目录的 .hermes/plans/ 下。)

你也可以通过自然对话与 skills 交互:

hermes chat --toolsets skills -q "What skills do you have?"
hermes chat --toolsets skills -q "Show me the axolotl skill"

渐进式披露​

Skills 使用一种节省 token 的加载模式:

Level 0: skills_list()           → [{name, description, category}, ...]   (~3k tokens)
Level 1: skill_view(name)        → Full content + metadata       (varies)
Level 2: skill_view(name, path)  → Specific reference file       (varies)

agent 只在真正需要时才加载完整的 skill 内容。

SKILL.md 格式​

---
name: my-skill
description: Brief description of what this skill does
version: 1.0.0
platforms: [macos, linux]     # Optional — restrict to specific OS platforms
metadata:
  hermes:
    tags: [python, automation]
    category: devops
    fallback_for_toolsets: [web]    # Optional — conditional activation (see below)
    requires_toolsets: [terminal]   # Optional — conditional activation (see below)
    config:                          # Optional — config.yaml settings
      - key: my.setting
        description: "What this controls"
        default: "value"
        prompt: "Prompt for setup"
---

# Skill Title

## When to Use
Trigger conditions for this skill.

## Procedure
1. Step one
2. Step two

## Pitfalls
- Known failure modes and fixes

## Verification
How to confirm it worked.

平台特定 Skills​

Skills 可以使用 platforms 字段将自身限制在特定操作系统上:

| | 值 | 匹配

| macos | macOS(Darwin)

| linux | Linux

| windows | Windows

platforms: [macos]            # macOS only (e.g., iMessage, Apple Reminders, FindMy)
platforms: [macos, linux]     # macOS and Linux

设置后,该 skill 会在不兼容的平台上自动从系统提示词、skills_list() 和斜杠命令中隐藏。若省略,则在所有平台上加载。

Skill 输出与媒体传递​

当 skill 响应(或任何 agent 响应)包含指向媒体文件的裸绝对路径时——例如 /home/user/screenshots/diagram.png——gateway 会自动检测到它,将其从可见文本中剥离,并以原生方式将文件传递给用户的聊天界面(Telegram 图片、Discord 附件等),而不是在消息中留下原始路径。

对于音频,[[audio_as_voice]] 指令会将音频文件提升为在支持该功能的平台(Telegram、WhatsApp)上的原生语音消息气泡。

强制文档式传递:[[as_document]]​

有时你需要与内联预览相反的效果:你希望文件作为可下载附件传递,而不是经过重新压缩的图片气泡。典型示例是高分辨率截图或图表——Telegram 的 sendPhoto 会将其重新压缩至约 200 KB、1280 px,严重影响可读性。通过 sendDocument 发送的 1-2 MB PNG 则保留原始字节完整无损。

如果响应(或其中任何文本——通常是最后一行)包含字面指令 [[as_document]],则从该响应中提取的每个媒体路径都会作为文档/文件附件传递,而不是图片气泡:

Here is your rendered chart:

/home/user/.hermes/cache/chart-q4-2025.png

[[as_document]]

该指令在传递前会被剥离,用户不会看到它。粒度有意设计为每个响应全有或全无:发出一次 [[as_document]],同一响应中的每个图片路径都会作为文档传递。这与 [[audio_as_voice]] 的作用范围一致。

在以下情况下从 skill 中使用它:

  • 你生成了用户需要作为文件的截图或图表(用于在其他工具中编辑、存档、完整分享)。
  • 默认的有损预览会遮蔽细节(小字体、像素精确的图表、对颜色敏感的渲染)。

没有单独文档路径的平台(如 SMS)会回退到其支持的任何附件机制。

条件激活(Fallback Skills)​

Skills 可以根据当前会话中可用的工具自动显示或隐藏自身。这对于fallback skills(回退 skills)最为有用——仅在高级工具不可用时才应出现的免费或本地替代方案。

metadata:
  hermes:
    fallback_for_toolsets: [web]      # Show ONLY when these toolsets are unavailable
    requires_toolsets: [terminal]     # Show ONLY when these toolsets are available
    fallback_for_tools: [web_search]  # Show ONLY when these specific tools are unavailable
    requires_tools: [terminal]        # Show ONLY when these specific tools are available

| | 字段 | 行为

| fallback_for_toolsets | 当列出的 toolsets 可用时,skill 隐藏。不可用时显示。

| fallback_for_tools | 同上,但检查单个工具而非 toolsets。

| requires_toolsets | 当列出的 toolsets 不可用时,skill 隐藏。可用时显示。

| requires_tools | 同上,但检查单个工具。

示例: 内置的 duckduckgo-search skill 使用 fallback_for_toolsets: [web]。当你设置了 FIRECRAWL_API_KEY 时,web toolset 可用,agent 使用 web_search——DuckDuckGo skill 保持隐藏。如果 API key 缺失,web toolset 不可用,DuckDuckGo skill 会自动作为 fallback 出现。

没有任何条件字段的 skills 行为与之前完全相同——始终显示。

加载时的安全设置​

Skills 可以声明所需的环境变量,而不会从发现列表中消失:

required_environment_variables:
  - name: TENOR_API_KEY
    prompt: Tenor API key
    help: Get a key from https://developers.google.com/tenor
    required_for: full functionality

当遇到缺失的值时,Hermes 仅在本地 CLI 中实际加载 skill 时才会安全地请求输入。你可以跳过设置并继续使用该 skill。消息平台不会在聊天中请求密钥——它们会告诉你改用本地的 hermes setup 或 ~/.hermes/.env。

一旦设置,声明的环境变量会自动传递到 execute_code 和 terminal 沙箱——skill 的脚本可以直接使用 $TENOR_API_KEY。对于非 skill 的环境变量,使用 terminal.env_passthrough 配置选项。详情参见环境变量传递。

Skill 配置设置​

Skills 还可以声明存储在 config.yaml 中的非密钥配置设置(路径、偏好项):

metadata:
  hermes:
    config:
      - key: myplugin.path
        description: Path to the plugin data directory
        default: "~/myplugin-data"
        prompt: Plugin data directory path

设置存储在 config.yaml 的 skills.config 下。hermes config migrate 会提示配置未设置的项,hermes config show 会显示它们。当 skill 加载时,其解析后的配置值会注入到上下文中,agent 会自动知晓已配置的值。

详情参见 Skill 设置 和创建 Skills——配置设置。

Skill 目录结构​

~/.hermes/skills/                  # Single source of truth
├── mlops/                         # Category directory
│   ├── axolotl/
│   │   ├── SKILL.md               # Main instructions (required)
│   │   ├── references/            # Additional docs
│   │   ├── templates/             # Output formats
│   │   ├── scripts/               # Helper scripts callable from the skill
│   │   ├── examples/              # Referenced example outputs
│   │   └── assets/                # Supplementary files
│   └── vllm/
│       └── SKILL.md
├── devops/
│   └── deploy-k8s/                # Agent-created skill
│       ├── SKILL.md
│       └── references/
├── .hub/                          # Skills Hub state
│   ├── lock.json
│   ├── quarantine/
│   └── audit.log
└── .bundled_manifest              # Tracks seeded bundled skills

通过第三方 URL 或 GitHub 安装时,Hermes 会安装 SKILL.md,以及其中明确引用且位于 references/、templates/、scripts/、assets/ 和 examples/ 下的文件。未引用的仓库文件不会被复制。Hermes 会扫描完整的隔离捆绑包,并在 skills/.hub/lock.json 中记录来源 URL、精确内容哈希、扫描器版本、发现项、时间戳,以及本次结果是新扫描还是缓存复用。

外部 Skill 目录​

如果你在 Hermes 之外维护 skills——例如,供多个 AI 工具使用的共享 ~/.agents/skills/ 目录——你可以告诉 Hermes 也扫描这些目录。

在 ~/.hermes/config.yaml 的 skills 部分下添加 external_dirs:

skills:
  external_dirs:
    - ~/.agents/skills
    - /home/shared/team-skills
    - ${SKILLS_REPO}/skills

路径支持 ~ 展开和 ${VAR} 环境变量替换。

工作原理​

  • 本地创建,就地更新:新的 agent 创建的 skills 写入 ~/.hermes/skills/。现有 skills 在找到的位置被修改,包括 external_dirs 下的 skills,当 agent 使用 skill_manage 操作(如 patch、edit、write_file、remove_file 或 delete)时。
  • 外部目录不是写保护边界:如果外部 skill 目录对 Hermes 进程可写,agent 管理的 skill 更新可以修改该目录中的文件。如果共享的外部 skills 必须保持只读,请使用文件系统权限或单独的 profile/toolset 设置。
  • 本地优先:如果同一 skill 名称同时存在于本地目录和外部目录中,本地版本优先。
  • 完整集成:外部 skills 出现在系统提示词索引、skills_list、skill_view 以及 /skill-name 斜杠命令中——与本地 skills 无异。
  • 不存在的路径会被静默跳过:如果配置的目录不存在,Hermes 会忽略它而不报错。适用于可能不在每台机器上都存在的可选共享目录。

示例​

~/.hermes/skills/               # Local (primary, read-write)
├── devops/deploy-k8s/
│   └── SKILL.md
└── mlops/axolotl/
    └── SKILL.md

~/.agents/skills/               # External (shared, mutable if writable)
├── my-custom-workflow/
│   └── SKILL.md
└── team-conventions/
    └── SKILL.md

所有四个 skills 都出现在你的 skill 索引中。如果你在本地创建一个名为 my-custom-workflow 的新 skill,它会遮蔽外部版本。

Skill 捆绑包​

Skill 捆绑包是将多个 skills 归组在单个斜杠命令下的小型 YAML 文件。当你运行 /<bundle-name> 时,捆绑包中列出的每个 skill 都会同时加载——当某个特定任务总是受益于同一组 skills 时非常有用。

快速示例​

# 为后端功能开发创建一个捆绑包
hermes bundles create backend-dev \
  --skill github-code-review \
  --skill test-driven-development \
  --skill github-pr-workflow \
  -d "Backend feature work — review, test, PR workflow"

然后在 CLI 或任何 gateway 平台中:

/backend-dev refactor the auth middleware

agent 接收到所有三个 skills 加载到一条用户消息中,斜杠命令后的任何文本都作为用户指令附加。

YAML 模式​

捆绑包存放在 ~/.hermes/skill-bundles/<slug>.yaml 中,格式如下:

name: backend-dev
description: Backend feature work — review, test, PR workflow.
skills:
  - github-code-review
  - test-driven-development
  - github-pr-workflow
instruction: |
  Always start by writing failing tests, then implement.
  Open the PR through the standard workflow with co-author tags.

字段说明:

  • name(可选——默认为文件名主干)——捆绑包的显示名称。规范化为连字符 slug 用于斜杠命令(Backend Dev → /backend-dev)。
  • description(可选)——在 /bundles 和 hermes bundles list 中显示的简短文本。
  • skills(必填,非空列表)——skill 名称或相对于你的 skills 目录的路径。使用与 /<skill-name> 相同的标识符。
  • instruction(可选)——附加在加载的 skill 内容前的额外指导。适用于固化"我们总是这样一起使用这些 skills"的方式。

管理捆绑包​

# 列出所有已安装的捆绑包
hermes bundles list

# 查看某个捆绑包
hermes bundles show backend-dev

# 交互式创建捆绑包(省略 --skill 标志以逐行输入)
hermes bundles create research

# 覆盖现有捆绑包
hermes bundles create backend-dev --skill ... --force

# 删除捆绑包
hermes bundles delete backend-dev

# 重新扫描 ~/.hermes/skill-bundles/ 并报告变更
hermes bundles reload

在聊天会话中,/bundles 会列出每个已安装的捆绑包及其 skills。

行为​

  • 当 slug 冲突时,捆绑包优先于单个 skills。 如果你将捆绑包命名为 research,同时也有一个名为 research 的 skill,/research 会调用捆绑包。这是有意为之——你通过命名选择了捆绑包。
  • 缺失的 skills 会被跳过,而不是致命错误。 如果捆绑包列出了 skill-foo 但你未安装它,捆绑包仍会加载能解析的 skills,agent 会收到一条列出跳过内容的说明。
  • 捆绑包在每个界面都有效——交互式 CLI、TUI、仪表板聊天以及每个 gateway 平台(Telegram、Discord、Slack……)——因为调度与单个 skill 命令集中在同一位置。
  • 捆绑包不会使 prompt 缓存失效。 它们在调用时生成一条新的用户消息,与 /<skill-name> 的方式相同——不修改系统提示词。

捆绑包优于逐个手动安装 skill 的场景​

在以下情况下使用捆绑包:

  • 你总是为某个重复任务配对相同的 skills(/backend-dev、/release-prep、/incident-response)。
  • 你想要比依次输入多个 /skill 调用更简洁的心智模型。
  • 你想通过将捆绑包 YAML 提交到共享 dotfiles 仓库并符号链接到 ~/.hermes/skill-bundles/ 来发布团队范围的"任务配置文件"。

捆绑包只是一个 YAML 别名——它不会为你安装 skills。Skills 本身必须已经存在(在 ~/.hermes/skills/ 或外部 skill 目录中)。否则捆绑包调用只会跳过缺失的 skills。

Agent 管理的 Skills(skill_manage 工具)​

agent 可以通过 skill_manage 工具创建、更新和删除自己的 skills。这是 agent 的程序性记忆——当它找到一个非平凡的工作流时,它会将该方法保存为 skill 以供将来复用。

Agent 创建 Skills 的时机​

  • 成功完成复杂任务后(5+ 次工具调用)
  • 遇到错误或死路并找到可行路径时
  • 用户纠正了其方法时
  • 发现了非平凡的工作流时

操作​

| | 操作 | 用途 | 关键参数

| create | 从头创建新 skill | name、content(完整 SKILL.md)、可选 category

| patch | 针对性修复(首选) | name、old_string、new_string

| edit | 重大结构性重写 | name、content(完整 SKILL.md 替换)

| delete | 完全删除一个 skill | name

| write_file | 添加/更新支持文件 | name、file_path、file_content

| remove_file | 删除支持文件 | name、file_path

提示

patch 操作是更新的首选方式——它比 edit 更节省 token,因为工具调用中只出现变更的文本。

Skills Hub​

从在线注册表、skills.sh、直接的知名 skill 端点以及官方可选 skills 中浏览、搜索、安装和管理 skills。

常用命令​

hermes skills browse                              # Browse all hub skills (official first)
hermes skills browse --source official            # Browse only official optional skills
hermes skills search kubernetes                   # Search all sources
hermes skills search react --source skills-sh     # Search the skills.sh directory
hermes skills search https://mintlify.com/docs --source well-known
hermes skills inspect openai/skills/k8s           # Preview before installing
hermes skills install openai/skills/k8s           # Install with security scan
hermes skills install official/security/1password
hermes skills install skills-sh/vercel-labs/json-render/json-render-react --force
hermes skills install well-known:https://mintlify.com/docs/.well-known/skills/mintlify
hermes skills install https://sharethis.chat/SKILL.md              # 直接 URL(含引用的支持文件)
hermes skills install https://example.com/SKILL.md --name my-skill # Override name when frontmatter has none
hermes skills list --source hub                   # List hub-installed skills
hermes skills check                               # Check installed hub skills for upstream updates
hermes skills update                              # Reinstall hub skills with upstream changes when needed
hermes skills audit                               # Re-scan all hub skills for security
hermes skills uninstall k8s                       # Remove a hub skill
hermes skills reset google-workspace              # Un-stick a bundled skill from "user-modified" (see below)
hermes skills reset google-workspace --restore    # Also restore the bundled version, deleting your local edits
hermes skills publish skills/my-skill --to github --repo owner/repo
hermes skills snapshot export setup.json          # Export skill config
hermes skills tap add myorg/skills-repo           # Add a custom GitHub source

支持的 hub 来源​

| | 来源 | 示例 | 说明

| official | official/security/1password | Hermes 随附的可选 skills。

| skills-sh | skills-sh/vercel-labs/agent-skills/vercel-react-best-practices | 可通过 hermes skills search <query> --source skills-sh 搜索。当 skills.sh slug 与仓库文件夹不同时,Hermes 会解析别名式 skills。

| well-known | well-known:https://mintlify.com/docs/.well-known/skills/mintlify | 直接从网站的 /.well-known/skills/index.json 提供的 skills。使用站点或文档 URL 搜索。

| url | https://sharethis.chat/SKILL.md | 指向 SKILL.md 及其明确引用的支持文件的直接 HTTP(S) URL。名称解析顺序:frontmatter → URL slug → 交互式提示 → --name 标志。

| github | openai/skills/k8s | 直接从 GitHub 仓库/路径安装以及基于 GitHub 的自定义 tap。

| clawhub、lobehub、browse-sh | 来源特定标识符 | 社区或市场集成。

集成的 hub 和注册表​

Hermes 目前与以下 skills 生态系统和发现来源集成:

1. 官方可选 skills(official)​

这些 skills 在 Hermes 仓库中维护,以内置信任级别安装。

  • 目录:官方可选 Skills 目录
  • 仓库中的来源:optional-skills/
  • 示例:
hermes skills browse --source official
hermes skills install official/security/1password

2. skills.sh(skills-sh)​

这是 Vercel 的公共 skills 目录。Hermes 可以直接搜索它、查看 skill 详情页、解析别名式 slug,并从底层源仓库安装。

  • 目录:skills.sh
  • CLI/工具仓库:vercel-labs/skills
  • Vercel 官方 skills 仓库:vercel-labs/agent-skills
  • 示例:
hermes skills search react --source skills-sh
hermes skills inspect skills-sh/vercel-labs/json-render/json-render-react
hermes skills install skills-sh/vercel-labs/json-render/json-render-react --force

3. Well-known skill 端点(well-known)​

这是基于 URL 的发现机制,来自发布 /.well-known/skills/index.json 的站点。它不是单一的集中式 hub——它是一种 Web 发现约定。

  • 示例实时端点:Mintlify docs skills index
  • 参考服务器实现:vercel-labs/skills-handler
  • 示例:
hermes skills search https://mintlify.com/docs --source well-known
hermes skills inspect well-known:https://mintlify.com/docs/.well-known/skills/mintlify
hermes skills install well-known:https://mintlify.com/docs/.well-known/skills/mintlify

4. 直接 GitHub skills(github)​

Hermes 可以直接从 GitHub 仓库和基于 GitHub 的 tap 安装。当你已知仓库/路径或想添加自己的自定义源仓库时非常有用。

默认 tap(无需任何设置即可浏览):

-

openai/skills

-

anthropics/skills

-

huggingface/skills

-

NVIDIA/skills — NVIDIA 官方验证的技能(带签名 skill.oms.sig 与治理用 skill-card.md)

-

VoltAgent/awesome-agent-skills

-

garrytan/gstack

-

示例:

hermes skills install openai/skills/k8s
hermes skills tap add myorg/skills-repo

5. ClawHub(clawhub)​

作为社区来源集成的第三方 skills 市场。

  • 站点:clawhub.ai
  • Hermes 来源 id:clawhub

6. LobeHub(lobehub)​

Hermes 可以从 LobeHub 的公共目录中搜索并将 agent 条目转换为可安装的 Hermes skills。

  • 站点:LobeHub
  • 公共 agents 索引:chat-agents.lobehub.com
  • 后端仓库:lobehub/lobe-chat-agents
  • Hermes 来源 id:lobehub

7. browse.sh(browse-sh)​

Hermes 与 browse.sh 集成,这是 Browserbase 的目录,包含 200+ 个针对特定站点的浏览器自动化 SKILL.md 文件(Airbnb、Amazon、arXiv、12306.cn、Etsy、Xero 等)。每个 skill 描述如何端到端驱动一个网站,适合与 Hermes 的浏览器工具以及你已安装的任何浏览器自动化 skills 配合使用。

  • 站点:browse.sh
  • 目录 API:https://browse.sh/api/skills
  • Hermes 来源 id:browse-sh
  • 信任级别:community
hermes skills search airbnb --source browse-sh
hermes skills inspect browse-sh/airbnb.com/search-listings-ddgioa
hermes skills install browse-sh/airbnb.com/search-listings-ddgioa

标识符使用 browse-sh/<hostname>/<task-id> 的形式,与 browse.sh 目录公开的 slug 匹配。内容通过每个 skill 的详情端点(/api/skills/<slug> → skillMdUrl)解析,而不是通过目录的 GitHub sourceUrl。

8. 直接 URL(url)​

直接从任何 HTTP(S) URL 安装 SKILL.md——当作者在自己的站点上托管 skill 时非常有用(无 hub 列表,无需输入 GitHub 路径)。Hermes 还会获取其中明确引用且位于 references/、templates/、scripts/、assets/ 和 examples/ 下的文件,然后扫描并安装完整捆绑包。

  • Hermes 来源 id:url
  • 标识符:URL 本身(无需前缀)
  • 范围:SKILL.md 加上允许目录中明确引用的支持文件。Hermes 不会枚举或复制托管站点上的其他文件。
hermes skills install https://sharethis.chat/SKILL.md
hermes skills install https://example.com/my-skill/SKILL.md --category productivity

名称解析顺序:

  • SKILL.md YAML frontmatter 中的 name: 字段(推荐——每个格式良好的 skill 都有)。
  • URL 路径中的父目录名称(例如 .../my-skill/SKILL.md → my-skill,或 .../my-skill.md → my-skill),当它是有效标识符(^[a-z][a-z0-9_-]*$)时。
  • 在有 TTY 的终端上的交互式提示。
  • 在非交互式界面(TUI 内的 /skills install 斜杠命令、gateway 平台、脚本)上,给出指向 --name 覆盖的清晰错误。
# Frontmatter 没有名称且 URL slug 无意义——手动提供:
hermes skills install https://example.com/SKILL.md --name sharethis-chat

# 或在聊天会话中:
/skills install https://example.com/SKILL.md --name sharethis-chat

信任级别始终为 community——与所有其他来源一样运行相同的安全扫描。URL 作为安装标识符存储,因此当你想刷新时,hermes skills update 会自动从同一 URL 重新获取。

安全扫描与 --force​

所有通过 hub 安装的 skills 都经过安全扫描器检查,检测数据泄露、prompt 注入、破坏性命令、供应链信号及其他威胁。

hermes skills inspect ... 现在还会在可用时显示上游元数据:

  • 仓库 URL
  • skills.sh 详情页 URL
  • 安装命令
  • 每周安装量
  • 上游安全审计状态
  • well-known 索引/端点 URL

当你已审查第三方 skill 并希望覆盖非危险性策略阻止时,使用 --force:

hermes skills install skills-sh/anthropics/skills/pdf --force

重要行为:

  • --force 可以覆盖谨慎/警告类发现的策略阻止。
  • --force 不能覆盖 dangerous 扫描结论。
  • 官方可选 skills(official/...)被视为内置信任,不显示第三方警告面板。

信任级别​

| | 级别 | 来源 | 策略

| builtin | 随 Hermes 附带 | 始终受信任

| official | 仓库中的 optional-skills/ | 内置信任,无第三方警告

| trusted | 受信任的注册表/仓库,如 openai/skills、anthropics/skills、huggingface/skills、NVIDIA/skills | 比社区来源更宽松的策略

| community | 其他所有来源(skills.sh、well-known 端点、自定义 GitHub 仓库、大多数市场) | 非危险性发现可用 --force 覆盖;dangerous 结论保持阻止

更新生命周期​

hub 现在跟踪足够的来源信息以重新检查已安装 skills 的上游副本:

hermes skills check          # Report which installed hub skills changed upstream
hermes skills update         # Reinstall only the skills with updates available
hermes skills update react   # Update one specific installed hub skill

这使用存储的来源标识符加上当前上游捆绑包内容哈希来检测漂移。

GitHub 速率限制

Skills hub 操作使用 GitHub API,未认证用户的速率限制为每小时 60 次请求。如果在安装或搜索时看到速率限制错误,请在 .env 文件中设置 GITHUB_TOKEN 以将限制提高到每小时 5,000 次请求。发生此情况时,错误消息会包含可操作的提示。

发布自定义 skill tap​

如果你想分享一组精选的 skills——为你的团队、组织或公开分享——你可以将它们发布为 tap:其他 Hermes 用户通过 hermes skills tap add <owner/repo> 添加的 GitHub 仓库。无需服务器,无需注册表注册,无需发布流水线。只需一个包含 SKILL.md 文件的目录。

仓库布局​

tap 是任何 GitHub 仓库(公开或私有——私有仓库需要 GITHUB_TOKEN),布局如下:

owner/repo
├── skills/                       # default path; configurable per-tap
│   ├── my-workflow/
│   │   ├── SKILL.md              # required
│   │   ├── references/           # optional supporting files
│   │   ├── templates/
│   │   └── scripts/
│   ├── another-skill/
│   │   └── SKILL.md
│   └── third-skill/
│       └── SKILL.md
└── README.md                     # optional but helpful

规则:

  • 每个 skill 存放在 tap 根路径(默认 skills/)下的独立目录中。
  • 目录名成为 skill 的安装 slug。
  • 每个 skill 目录必须包含一个带有标准 SKILL.md frontmatter 的 SKILL.md(name、description,以及可选的 metadata.hermes.tags、version、author、platforms、metadata.hermes.config)。
  • references/、templates/、scripts/、assets/ 等子目录在安装时与 SKILL.md 一起下载。
  • 目录名以 . 或 _ 开头的 skills 会被忽略。

Hermes 通过列出 tap 路径的每个子目录并探测每个目录中的 SKILL.md 来发现 skills。

最小 tap 示例​

my-org/hermes-skills
└── skills/
    └── deploy-runbook/
        └── SKILL.md

skills/deploy-runbook/SKILL.md:

---
name: deploy-runbook
description: Our deployment runbook — services, rollback, Slack channels
version: 1.0.0
author: My Org Platform Team
metadata:
  hermes:
    tags: [deployment, runbook, internal]
---

# Deploy Runbook

Step 1: ...

将其推送到 GitHub 后,任何 Hermes 用户都可以订阅并安装:

hermes skills tap add my-org/hermes-skills
hermes skills search deploy
hermes skills install my-org/hermes-skills/deploy-runbook

非默认路径​

如果你的 skills 不在 skills/ 下(当你向现有项目添加 skills/ 子树时很常见),请编辑 ~/.hermes/.hub/taps.json 中的 tap 条目:

{
  "taps": [
    {"repo": "my-org/platform-docs", "path": "internal/skills/"}
  ]
}

hermes skills tap add CLI 默认将新 tap 的 path 设为 "skills/";如果需要不同路径,请直接编辑该文件。hermes skills tap list 显示每个 tap 的有效路径。

直接安装单个 skills(无需添加 tap)​

用户也可以从任何公开 GitHub 仓库安装单个 skill,而无需将整个仓库添加为 tap:

hermes skills install owner/repo/skills/my-workflow

当你想分享一个 skill 而不要求用户订阅你的整个注册表时非常有用。

tap 的信任级别​

新 tap 默认分配 community 信任级别。从中安装的 skills 经过标准安全扫描,首次安装时显示第三方警告面板。如果你的组织或广泛受信任的来源应获得更高信任,请将其仓库添加到 tools/skills_hub.py 中的 TRUSTED_REPOS(需要 Hermes 核心 PR)。

Tap 管理​

hermes skills tap list                                # show all configured taps
hermes skills tap add myorg/skills-repo               # add (default path: skills/)
hermes skills tap remove myorg/skills-repo            # remove

在运行中的会话内:

/skills tap list
/skills tap add myorg/skills-repo
/skills tap remove myorg/skills-repo

Tap 存储在 ~/.hermes/.hub/taps.json 中(按需创建)。

捆绑 skill 更新(hermes skills reset)​

Hermes 在仓库的 skills/ 中附带一组捆绑 skills。在安装时以及每次 hermes update 时,同步过程会将这些 skills 复制到 ~/.hermes/skills/ 中,并在 ~/.hermes/skills/.bundled_manifest 记录一个清单,将每个 skill 名称映射到同步时的内容哈希(origin hash)。

每次同步时,Hermes 重新计算本地副本的哈希并与 origin hash 比较:

  • 未更改 → 可以安全拉取上游变更,复制新的捆绑版本,记录新的 origin hash。
  • 已更改 → 视为用户修改并永久跳过,因此你的编辑不会被覆盖。

这种保护机制很好,但有一个棘手的边缘情况。如果你编辑了一个捆绑 skill,后来想通过从 ~/.hermes/hermes-agent/skills/ 复制粘贴来放弃更改并回到捆绑版本,清单仍然保存着上次成功同步时的旧 origin hash。你新复制粘贴的内容(当前捆绑哈希)与那个过时的 origin hash 不匹配,因此同步继续将其标记为用户修改。

hermes skills reset 是解决此问题的方法:

# 安全:清除此 skill 的清单条目。你当前的副本被保留,
# 但下次同步会重新以其为基准,使未来的更新正常工作。
hermes skills reset google-workspace

# 完全恢复:同时删除你的本地副本并重新复制当前捆绑版本。
# 当你想要恢复原始上游 skill 时使用此选项。
hermes skills reset google-workspace --restore

# 非交互式(例如在脚本或 TUI 模式中)——跳过 --restore 确认。
hermes skills reset google-workspace --restore --yes

同样的命令也可以作为斜杠命令在聊天中使用:

/skills reset google-workspace
/skills reset google-workspace --restore

Profiles

每个 profile 在其自己的 HERMES_HOME 下有自己的 .bundled_manifest,因此 hermes -p coder skills reset <name> 只影响该 profile。

斜杠命令(在聊天中)​

所有相同的命令都可以使用 /skills 执行:

/skills browse
/skills search react --source skills-sh
/skills search https://mintlify.com/docs --source well-known
/skills inspect skills-sh/vercel-labs/json-render/json-render-react
/skills install openai/skills/skill-creator --force
/skills check
/skills update
/skills reset google-workspace
/skills list

官方可选 skills 仍使用 official/security/1password 和 official/migration/openclaw-migration 等标识符。

持久化记忆

Hermes Agent 拥有有界、经过整理的记忆,可跨会话持久保存。这使它能够记住你的偏好、项目、环境以及已学到的内容。

工作原理​

两个文件构成 Agent 的记忆:

| | 文件 | 用途 | 字符上限

| MEMORY.md | Agent 的个人笔记——环境事实、约定、已学内容 | 2,200 字符(约 800 tokens)

| USER.md | 用户档案——你的偏好、沟通风格、期望 | 1,375 字符(约 500 tokens)

两个文件均存储于 ~/.hermes/memories/,在会话开始时以冻结快照的形式注入系统 prompt(提示词)。Agent 通过 memory 工具管理自身记忆——可添加、替换或删除条目。

信息

字符上限使记忆保持聚焦。当记忆已满时,Agent 会整合或替换条目以腾出空间存放新信息。

记忆在系统 Prompt 中的呈现方式​

每次会话开始时,记忆条目从磁盘加载并以冻结块的形式渲染到系统 prompt 中:

══════════════════════════════════════════════
MEMORY (your personal notes) [67% — 1,474/2,200 chars]
══════════════════════════════════════════════
User's project is a Rust web service at ~/code/myapi using Axum + SQLx
§
This machine runs Ubuntu 22.04, has Docker and Podman installed
§
User prefers concise responses, dislikes verbose explanations

格式包含:

  • 标头,显示存储类型(MEMORY 或 USER PROFILE)
  • 使用百分比和字符计数,让 Agent 了解容量
  • 以 §(节符)分隔的各条目
  • 条目可以是多行

冻结快照模式: 系统 prompt 注入在会话开始时捕获一次,会话中途不会改变。这是有意为之——目的是保留 LLM 的前缀缓存以提升性能。当 Agent 在会话期间添加或删除记忆条目时,更改会立即持久化到磁盘,但要到下一次会话开始时才会出现在系统 prompt 中。工具响应始终显示实时状态。

Memory 工具操作​

Agent 使用 memory 工具执行以下操作:

  • add — 添加新的记忆条目
  • replace — 用更新内容替换现有条目(通过 old_text 进行子字符串匹配)
  • remove — 删除不再相关的条目(通过 old_text 进行子字符串匹配)

没有 read 操作——记忆内容在会话开始时自动注入系统 prompt。Agent 将其记忆作为对话上下文的一部分来查看。

子字符串匹配​

replace 和 remove 操作使用简短的唯一子字符串匹配——不需要完整的条目文本。old_text 参数只需是能唯一标识某一条目的子字符串即可:

# If memory contains "User prefers dark mode in all editors"
memory(action="replace", target="memory",
       old_text="dark mode",
       content="User prefers light mode in VS Code, dark mode in terminal")

如果子字符串匹配到多个条目,则返回错误,要求提供更具体的匹配内容。

两个目标说明​

memory — Agent 的个人笔记​

用于 Agent 需要记住的环境、工作流及经验教训相关信息:

  • 环境事实(操作系统、工具、项目结构)
  • 项目约定和配置
  • 发现的工具怪癖与变通方法
  • 已完成任务的日记条目
  • 有效的技能和技术

user — 用户档案​

用于记录用户的身份、偏好和沟通风格:

  • 姓名、角色、时区
  • 沟通偏好(简洁 vs 详细、格式偏好)
  • 反感的事项和需要避免的内容
  • 工作流习惯
  • 技术水平

什么该保存,什么该跳过​

主动保存这些内容​

Agent 会自动保存——无需你主动要求。当它学到以下内容时会保存:

  • 用户偏好: "我更喜欢 TypeScript 而非 JavaScript" → 保存到 user
  • 环境事实: "此服务器运行 Debian 12,安装了 PostgreSQL 16" → 保存到 memory
  • 纠正信息: "Docker 命令不要用 sudo,用户已在 docker 组中" → 保存到 memory
  • 约定: "项目使用 tab 缩进、120 字符行宽、Google 风格 docstring" → 保存到 memory
  • 已完成的工作: "2026-01-15 将数据库从 MySQL 迁移到 PostgreSQL" → 保存到 memory
  • 明确请求: "记住我的 API 密钥每月轮换一次" → 保存到 memory

跳过这些内容​

  • 琐碎/显而易见的信息: "用户询问了 Python"——太模糊,没有实用价值
  • 容易重新发现的事实: "Python 3.12 支持 f-string 嵌套"——可以网络搜索
  • 原始数据转储: 大型代码块、日志文件、数据表——对记忆来说太大
  • 会话特定的临时内容: 临时文件路径、一次性调试上下文
  • 已在上下文文件中的信息: SOUL.md 和 AGENTS.md 的内容

容量管理​

记忆有严格的字符上限,以保持系统 prompt 的有界性:

| | 存储 | 上限 | 典型条目数

| memory | 2,200 字符 | 8-15 条

| user | 1,375 字符 | 5-10 条

记忆已满时的处理​

当你尝试添加会超出上限的条目时,工具返回错误:

{
  "success": false,
  "error": "Memory at 2,100/2,200 chars. Adding this entry (250 chars) would exceed the limit. Replace or remove existing entries first.",
  "current_entries": ["..."],
  "usage": "2,100/2,200"
}

Agent 应当:

  • 读取当前条目(显示在错误响应中)
  • 识别可以删除或整合的条目
  • 使用 replace 将相关条目合并为更简短的版本
  • 然后 add 新条目

最佳实践: 当记忆使用率超过 80%(在系统 prompt 标头中可见)时,在添加新条目之前先整合现有条目。例如,将三个独立的"项目使用 X"条目合并为一个综合性的项目描述条目。

优质记忆条目的实际示例​

紧凑、信息密度高的条目效果最佳:

# Good: Packs multiple related facts
User runs macOS 14 Sonoma, uses Homebrew, has Docker Desktop and Podman. Shell: zsh with oh-my-zsh. Editor: VS Code with Vim keybindings.

# Good: Specific, actionable convention
Project ~/code/api uses Go 1.22, sqlc for DB queries, chi router. Run tests with 'make test'. CI via GitHub Actions.

# Good: Lesson learned with context
The staging server (10.0.1.50) needs SSH port 2222, not 22. Key is at ~/.ssh/staging_ed25519.

# Bad: Too vague
User has a project.

# Bad: Too verbose
On January 5th, 2026, the user asked me to look at their project which is
located at ~/code/api. I discovered it uses Go version 1.22 and...

重复防护​

记忆系统会自动拒绝完全重复的条目。如果你尝试添加已存在的内容,系统返回成功并附带"未添加重复项"的消息。

安全扫描​

记忆条目在被接受之前会扫描注入和数据外泄模式,因为它们会被注入系统 prompt。匹配威胁模式(prompt 注入、凭据外泄、SSH 后门)或包含不可见 Unicode 字符的内容将被拦截。

会话搜索​

除 MEMORY.md 和 USER.md 之外,Agent 还可以使用 session_search 工具搜索过去的对话:

  • 所有 CLI 和消息会话均存储在 SQLite(~/.hermes/state.db)中,支持 FTS5 全文搜索
  • 搜索查询返回数据库中的实际消息——无 LLM 摘要,无截断
  • Agent 可以找到数周前讨论过的内容,即使它们不在活跃记忆中
  • Agent 还可以在找到的任意会话中向前或向后滚动
hermes sessions list    # 浏览过去的会话

有关三种调用形式(发现 / 滚动 / 浏览)和响应格式,请参阅会话搜索工具。

session_search 与 memory 的对比​

| | 特性 | 持久化记忆 | 会话搜索

| 容量 | 约 1,300 tokens 总计 | 无限制(所有会话)

| 速度 | 即时(在系统 prompt 中) | 约 20ms FTS5 查询,约 1ms 滚动

| 成本 | 每次 prompt 均有 token 开销 | 免费——无 LLM 调用

| 使用场景 | 始终可用的关键事实 | 查找特定的过去对话

| 管理方式 | 由 Agent 手动整理 | 自动——所有会话均存储

| Token 开销 | 每次会话固定(约 1,300 tokens) | 按需(仅在搜索时产生)

记忆用于应始终在上下文中的关键事实。会话搜索用于"我们上周讨论过 X 吗?"这类需要 Agent 从过去对话中回忆具体内容的查询。

同模型审查的推理强度​

后台审查与主会话使用同一模型时,始终继承主会话的推理强度。auxiliary.background_review.reasoning_effort 不会覆盖该设置;auto 路由和显式指定主会话的 provider/model 都遵循此规则。

分支创建时,推理设置、系统 prompt、完整会话快照和工具定义保持与主会话逐字节一致,以复用 prompt 缓存前缀。只改变审查的推理强度会破坏这种一致性。没有用于同模型审查的独立推理强度开关。

若要减少审查工作而不改变主会话的推理强度,可以调整 memory.nudge_interval / skills.creation_nudge_interval,通过 auxiliary.background_review.enabled: false 禁用自动审查,或将审查路由到其他模型。其他模型使用会话摘要,不共享主会话的预热缓存前缀;其任务推理强度的独立问题见 #94825。频率和路由控制并不会分离同模型审查的推理强度。

配置​

# In ~/.hermes/config.yaml
memory:
  memory_enabled: true
  user_profile_enabled: true
  memory_char_limit: 2200   # ~800 tokens
  user_char_limit: 1375     # ~500 tokens

外部记忆提供商​

对于超出 MEMORY.md 和 USER.md 范围的更深层持久化记忆,Hermes 内置了 8 个外部记忆提供商插件——包括 Honcho、OpenViking、Mem0、Hindsight、Holographic、RetainDB、ByteRover 和 Supermemory。

外部提供商与内置记忆并行运行(而非替代),并增加了知识图谱、语义搜索、自动事实提取和跨会话用户建模等能力。

hermes memory setup      # 选择并配置提供商
hermes memory status     # 查看当前激活状态

有关每个提供商的完整详情、设置说明和对比,请参阅记忆提供商指南。

定时任务(Cron)

使用自然语言或 cron 表达式调度自动运行的任务。Hermes 通过单一 cronjob 工具暴露 cron 管理能力,采用动作式操作,而非分散的 schedule/list/remove 工具。

Cron 当前能做什么​

Cron 任务可以:

  • 调度一次性或周期性任务
  • 暂停、恢复、编辑、触发和删除任务
  • 为任务附加零个、一个或多个 skill
  • 将结果回传到来源会话、本地文件或已配置的平台目标
  • 在全新的 agent 会话中运行,使用正常的静态工具列表
  • 以无 agent 模式运行——按计划执行脚本,其 stdout 原样投递,零 LLM 参与(参见下方无 agent 模式章节)
  • 由外部事件触发——设置了 cron_job 的 webhook 路由会在事情发生的那一刻(PR 收到反馈、服务发出告警)立即触发任务,而不是等待下一次定时 tick。参见事件触发的 Cron 任务。

所有这些功能均可通过 cronjob 工具由 Hermes 自身使用,因此你可以用自然语言创建、暂停、编辑和删除任务——无需 CLI。

注意

Cron 运行的会话不能递归创建更多 cron 任务。Hermes 在 cron 执行内部禁用了 cron 管理工具,以防止失控的调度循环。

创建定时任务​

在聊天中使用 /cron​

/cron add 30m "Remind me to check the build"
/cron add "every 2h" "Check server status"
/cron add "every 1h" "Summarize new feed items" --skill blogwatcher
/cron add "every 1h" "Use both skills and combine the result" --skill blogwatcher --skill maps

从独立 CLI​

hermes cron create "every 2h" "Check server status"
hermes cron create "every 1h" "Summarize new feed items" --skill blogwatcher
hermes cron create "every 1h" "Use both skills and combine the result" \
  --skill blogwatcher \
  --skill maps \
  --name "Skill combo"

通过自然对话​

直接向 Hermes 描述:

Every morning at 9am, check Hacker News for AI news and send me a summary on Telegram.

Hermes 会在内部使用统一的 cronjob 工具。

附带 skill 的 cron 任务​

Cron 任务可以在运行 prompt(提示词)之前加载一个或多个 skill。

单个 skill​

cronjob(
    action="create",
    skill="blogwatcher",
    prompt="Check the configured feeds and summarize anything new.",
    schedule="0 9 * * *",
    name="Morning feeds",
)

多个 skill​

Skill 按顺序加载。Prompt 作为任务指令叠加在这些 skill 之上。

cronjob(
    action="create",
    skills=["blogwatcher", "maps"],
    prompt="Look for new local events and interesting nearby places, then combine them into one short brief.",
    schedule="every 6h",
    name="Local brief",
)

当你希望定时 agent 继承可复用的工作流,而不必将完整的 skill 文本塞入 cron prompt 本身时,这非常有用。

在指定项目目录中运行任务​

Cron 任务默认与任何代码仓库脱离运行——不加载 AGENTS.md、CLAUDE.md 或 .cursorrules,终端/文件/代码执行工具从 gateway 启动时的工作目录运行。传入 --workdir(CLI)或 workdir=(工具调用)可更改此行为:

# 独立 CLI(schedule 和 prompt 为位置参数)
hermes cron create "every 1d at 09:00" \
  "Audit open PRs, summarize CI health, and post to #eng" \
  --workdir /home/me/projects/acme
# 在聊天中,通过 cronjob 工具
cronjob(
    action="create",
    schedule="every 1d at 09:00",
    workdir="/home/me/projects/acme",
    prompt="Audit open PRs, summarize CI health, and post to #eng",
)

设置 workdir 后:

  • 该目录中的 AGENTS.md、CLAUDE.md 和 .cursorrules 会被注入系统 prompt(发现顺序与交互式 CLI 相同)
  • terminal、read_file、write_file、patch、search_files 和 execute_code 均以该目录为工作目录
  • 路径必须是已存在的绝对目录——相对路径和不存在的目录在创建/更新时会被拒绝
  • 编辑时传入 --workdir ""(或工具中的 workdir="")可清除该设置并恢复原有行为

隔离

每次 agent 运行都会将其 workdir 绑定到该次运行的唯一任务标识。设置了 workdir 的任务因此可使用正常的并行池,不会修改进程全局终端状态,也不会在并发运行之间泄漏路径。如需限制 cron 的总并发量,请设置 cron.max_parallel_jobs。

在指定 profile 中运行 cron 任务​

默认情况下,cron 任务继承创建它的 gateway/CLI 所属的 Hermes profile。传入 --profile <name>(CLI)或 profile=(cronjob 工具)可将任务重定向到不同的 profile——调度器会解析该 profile 的 HERMES_HOME,在运行期间临时切换到该 profile,加载其 .env 和 config.yaml,并在其中执行任务:

# 将任务固定到 `night-ops` profile,无论在哪里调度
hermes cron create "every 1d at 03:00" \
  "Tail the security log and flag anomalies" \
  --profile night-ops
# 在聊天中,通过 cronjob 工具
cronjob(
    action="create",
    schedule="every 1d at 03:00",
    prompt="Tail the security log and flag anomalies",
    profile="night-ops",
)

使用 --profile default 可显式固定到根 Hermes profile。指定的 profile 必须已存在;调度器不会动态创建 profile。在 cron edit 时清除 profile 固定,传入空字符串(--profile "" 或 profile="")——任务将恢复在调度器当前所在的 profile 中运行。

如果固定的 profile 后来被删除,调度器会记录警告并回退到在当前 profile 中运行该任务,而不是崩溃——因此过期的 profile 引用不会卡住任务。

串行化

设置了 profile 的任务也串行运行,原因与 workdir 固定任务相同:切换 HERMES_HOME 是进程全局变更,两个 profile 固定任务并行运行会产生竞争。未固定的任务仍在正常并行池中运行。

编辑任务​

无需删除并重建任务来修改它们。

任务引用

下方(以及生命周期操作中)的 <job_id> 占位符也接受任务名称(不区分大小写)——当你记得 morning-digest 但不记得十六进制 ID 时很方便。精确的任务 ID 优先于名称匹配;如果引用不是 ID 且名称匹配到多个任务,命令会拒绝执行并打印候选 ID 供你消歧义。

聊天​

/cron edit <job_id> --schedule "every 4h"
/cron edit <job_id> --prompt "Use the revised task"
/cron edit <job_id> --skill blogwatcher --skill maps
/cron edit <job_id> --remove-skill blogwatcher
/cron edit <job_id> --clear-skills

独立 CLI​

hermes cron edit <job_id> --schedule "every 4h"
hermes cron edit <job_id> --prompt "Use the revised task"
hermes cron edit <job_id> --skill blogwatcher --skill maps
hermes cron edit <job_id> --add-skill maps
hermes cron edit <job_id> --remove-skill blogwatcher
hermes cron edit <job_id> --clear-skills

注意:

  • 重复使用 --skill 会替换任务已附加的 skill 列表
  • --add-skill 追加到现有列表,不替换
  • --remove-skill 删除指定的已附加 skill
  • --clear-skills 删除所有已附加的 skill

生命周期操作​

Cron 任务现在拥有比创建/删除更完整的生命周期。

聊天​

/cron list
/cron pause <job_id>
/cron resume <job_id>
/cron run <job_id>
/cron remove <job_id>

独立 CLI​

hermes cron list
hermes cron pause <job_id>
hermes cron resume <job_id>
hermes cron run <job_id>
hermes cron remove <job_id>
hermes cron status
hermes cron tick

各操作说明:

  • pause — 保留任务但停止调度
  • resume — 重新启用任务并计算下次运行时间
  • run — 在下次调度器 tick 时触发任务
  • remove — 彻底删除任务

工作原理​

Cron 执行由 gateway 守护进程处理。 Gateway 每 60 秒 tick 一次调度器,在隔离的 agent 会话中运行到期的任务。

hermes gateway install     # 安装为用户服务
sudo hermes gateway install --system   # Linux:服务器开机启动的系统服务
hermes gateway             # 或在前台运行

hermes cron list
hermes cron status

Gateway 调度器行为​

每次 tick 时,Hermes:

  • 从 ~/.hermes/cron/jobs.json 加载任务
  • 对照当前时间检查 next_run_at
  • 为每个到期任务启动全新的 AIAgent 会话
  • 可选地将一个或多个已附加的 skill 注入该新会话
  • 将 prompt 运行至完成
  • 投递最终响应
  • 更新运行元数据和下次调度时间

~/.hermes/cron/.tick.lock 文件锁可防止重叠的调度器 tick 重复运行同一批任务。

执行历史​

Hermes 会在执行器或调度提供程序分派之前,将每次已领取的 cron 尝试记录到当前 profile 的 ~/.hermes/cron/executions.db。尝试会依次进入 claimed、running,然后进入不可变的终态:completed、failed 或 unknown。重启后,只有原 PID 与进程启动时间指纹能够证明所有者已经消失时,Hermes 才会将遗留尝试标记为 unknown。未知尝试仅用于审计,绝不会自动重跑。

使用 hermes cron runs [job-id] --limit 20(别名:history)查看最近的尝试。终态历史有界,活动尝试不会被清理;快速备份也包含该账本。

投递选项​

调度任务时,你可以指定输出的去向:

| | 选项 | 说明 | 示例

| "origin" | 回传到任务创建的来源 | 消息平台上的默认值

| "local" | 仅保存到本地文件(~/.hermes/cron/output/) | CLI 上的默认值

| "telegram" | Telegram 主频道 | 使用 TELEGRAM_HOME_CHANNEL

| "telegram:123456" | 按 ID 指定的 Telegram 会话 | 直接投递

| "telegram:-100123:17585" | 指定 Telegram 话题 | chat_id:thread_id 格式

| "discord" | Discord 主频道 | 使用 DISCORD_HOME_CHANNEL

| "discord:#engineering" | 按频道名指定的 Discord 频道 | 按频道名

| "slack" | Slack 主频道 |

| "whatsapp" | WhatsApp 主账号 |

| "signal" | Signal |

| "matrix" | Matrix 主房间 |

| "mattermost" | Mattermost 主频道 |

| "email" | 邮件 |

| "sms" | 通过 Twilio 发送 SMS |

| "homeassistant" | Home Assistant(插件) | 使用 HASS_HOME_CHANNEL;需要 homeassistant 插件

| "dingtalk" | 钉钉 |

| "feishu" | 飞书/Lark |

| "wecom" | 企业微信 |

| "weixin" | 微信(WeChat) |

| "bluebubbles" | BlueBubbles(iMessage) |

| "qqbot" | QQ Bot(腾讯 QQ) |

| "all" | 扇出到所有已连接的主频道 | 触发时解析

| "telegram,discord" | 扇出到指定的一组频道 | 逗号分隔列表

| "origin,all" | 投递到来源加上所有其他已连接频道 | 可组合任意 token

Agent 的最终响应会自动投递,无需在 cron prompt 中调用 send_message。

路由意图(all)​

all 让你将一个 cron 任务发送到所有已配置的消息频道,无需逐一列举名称。它在触发时解析,因此在你配置 TELEGRAM_HOME_CHANNEL 之前创建的任务,会在下次 tick 时自动纳入 Telegram。

语义:all 展开为所有已配置主频道的平台。零个也没问题;任务只是没有投递目标,并在上游记录为投递失败。

all 可与显式目标组合。origin,all 投递到来源会话加上所有其他已连接的主频道,按 (platform, chat_id, thread_id) 去重。

Telegram cron 话题(TELEGRAM_CRON_THREAD_ID)​

启用 Telegram 话题模式后,根 DM 被保留为系统大厅——发送到那里的回复会被拒绝并附带大厅提示,reply_to_message_id 会被丢弃,因此你无法回复落在主聊天中的 cron 消息。

将 cron 指向专用的论坛话题:

  • 在 Telegram 中打开机器人 DM,创建一个名为 Cron 的话题。长按话题标题 → 复制链接;末尾的整数即为该话题的 message_thread_id。
  • 在 .env 中设置 TELEGRAM_CRON_THREAD_ID=<该 id>。

这仅适用于 cron 投递。TELEGRAM_HOME_CHANNEL_THREAD_ID(用于其他地方,如重启通知)不受影响。显式的 deliver="telegram:chat_id:thread_id" 目标仍优先于环境变量。对 cron 消息的回复现在会进入已有的话题会话,你可以直接在其中操作。

响应包装​

默认情况下,投递的 cron 输出会带有页眉和页脚,以便接收方知道这来自定时任务:

Cronjob Response: Morning feeds
-------------

<agent output here>

Note: The agent cannot see this message, and therefore cannot respond to it.

若要投递不带包装的原始 agent 输出,将 cron.wrap_response 设为 false:

# ~/.hermes/config.yaml
cron:
  wrap_response: false

可继续任务(回复 cron 投递)​

默认情况下,cron 投递是「发完即忘」的:消息发送出去,但不会进入聊天的对话历史,

因此如果你回复它,agent 并不记得自己说过什么。将任务设为可继续后,投递的简报

就变成一段你可以回复进去的对话——agent 会把简报保留在上下文中,而不会反问

「Task #2 是什么?」。

选择性启用,默认关闭。可在配置中全局启用,或通过 cronjob 工具的

attach_to_session 按任务启用(会覆盖该任务的全局设置):

# ~/.hermes/config.yaml
cron:
  mirror_delivery: false   # 设为 true 使 cron 投递可继续

行为为优先使用话题,范围限定在任务的来源聊天:

  • 支持话题的平台(Telegram 话题、Discord/Slack/Matrix 话题):每次投递都会新建

专用话题,并将简报植入该话题的会话中,因此在话题内回复即可带完整上下文继续。

  • 仅 DM 的平台(WhatsApp、Signal、SMS):不存在话题,因此简报会被镜像进

来源 DM 会话——DM 本身就是继续的载体。

只有来源聊天会被触及:扇出/广播目标(all、显式的其他聊天投递)永远不会被设为可继续。

平铺频道内继续(Slack)​

上面的优先话题行为每次投递都会新建专用话题。如果你希望可继续任务平铺落在频道

时间线中——不新建话题——将 Slack 的继续投递方式设为 in_channel:

# ~/.hermes/config.yaml
slack:
  cron_continuable_surface: in_channel   # 默认:"thread"
  reply_in_thread: false                 # 必需搭配(见下)
  require_mention: false                 # 纯文本回复即可继续任务

在 in_channel 模式下,简报作为普通的顶层频道消息投递(不新建话题),你的回复通过

频道的共享会话继续任务。三项设置协同工作:

  • cron_continuable_surface: in_channel——投递时跳过新建话题。
  • reply_in_thread: false(必需)——让机器人在频道中平铺回复你,并将其

归入简报所植入的同一个整频道会话。缺少它时继续功能仍可用,但回复会出现在话题里

(安全回退为话题式继续,绝不会丢失回复——网关会在启动时记录一条警告便于发现不匹配)。

  • require_mention: false(或将该频道加入 free_response_channels)——这样你

可以用纯文本消息回复;否则机器人只在你每次 @ 提及它时才被唤醒。

由于继续载体是整频道会话,它是共享的:频道里的其他闲聊——以及第二个可继续的

in_channel 任务——都会加入同一段滚动对话。这是「平铺在频道中」的固有取舍,与

reply_in_thread: false 用户已经接受的取舍相同;若希望每次投递的后续讨论相互隔离,

请使用默认的 thread 方式。

这目前是 Slack 的能力。其他平台接受该键,但会回退到 thread 方式(它们的继续原语

不同);该选择按平台设置,位于各平台的配置下。这是网关侧的配置项——/restart 即可

生效;无需重新安装 Slack 应用。

1:1 私信(DM)

cron_continuable_surface 是频道设置——1:1 私信没有「话题 vs 时间线」的区分

(私信本身就是平铺的),因此该键在私信中无效。决定私信 cron 投递是否可继续的是另一个

已有的独立开关 slack.dm_top_level_threads_as_sessions:

  • false——所有顶层私信共享同一个滚动私信会话,因此可继续的 cron 简报与你的回复

落在同一个会话里,任务得以带上下文继续。这正是私信中可继续 cron 所需要的。

  • true(默认)——每条顶层私信消息各自成为独立会话,因此对已投递简报的回复会开启

一个全新、不含该简报记录的会话。此模式下继续功能不可用(对 cron 或任何平铺投递皆然)。

所以,若要让 cron 任务在 1:1 私信中可继续,请设置

slack.dm_top_level_threads_as_sessions: false。私信不需要(也会忽略)

cron_continuable_surface。

静默抑制​

如果 agent 的最终响应以 [SILENT] 开头,投递将被完全抑制。输出仍会保存到本地以供审计(位于 ~/.hermes/cron/output/),但不会向投递目标发送任何消息。

这对于只在出现问题时才需要上报的监控任务很有用:

Check if nginx is running. If everything is healthy, respond with only [SILENT].
Otherwise, report the issue.

失败的任务无论 [SILENT] 标记如何都会投递——只有成功的运行才能被静默。

脚本超时​

预运行脚本(通过 script 参数附加)的默认超时为 120 秒。如果你的脚本需要更长时间——例如,包含随机延迟以避免类机器人的时序模式——可以增加此值:

# ~/.hermes/config.yaml
cron:
  script_timeout_seconds: 300   # 5 分钟

或设置 HERMES_CRON_SCRIPT_TIMEOUT 环境变量。解析顺序为:环境变量 → config.yaml → 默认 120 秒。

无 agent 模式(纯脚本任务)​

对于不需要 LLM 推理的周期性任务——经典的看门狗、磁盘/内存告警、心跳、CI ping——在创建时传入 no_agent=True。调度器按计划运行你的脚本,并直接投递其 stdout,完全跳过 agent:

hermes cron create "every 5m" \
  --no-agent \
  --script memory-watchdog.sh \
  --deliver telegram \
  --name "memory-watchdog"

语义:

  • 脚本 stdout(去除首尾空白)→ 原样作为消息投递。
  • stdout 为空 → 静默 tick,不投递。这是看门狗模式:"只在出现问题时才说话"。
  • 非零退出或超时 → 投递错误告警,确保损坏的看门狗不会静默失败。
  • 最后一行输出 {"wakeAgent": false} → 静默 tick(与 LLM 任务使用相同的门控)。
  • 无 token、无模型、无 provider 回退——任务永远不会触及推理层。

.sh/.bash 文件优先使用 PATH 中的 bash,不可用时回退到 /bin/bash(这对 Windows Git Bash 尤其重要);其他文件默认使用当前 Python 解释器(sys.executable)。脚本路径必须解析到 $HERMES_HOME/scripts/ 内部——只要解析后的目标仍位于该目录,相对路径、绝对路径和以 ~ 开头的路径都可以;逃逸该目录的路径会被拒绝。Python script 或 monitor_script 也可以通过在创建/编辑时传入 --interpreter ~/venvs/.../bin/python 来指定一个用户自管的 venv(用于 Hermes 运行时不携带的包)——参见使用你自己的 Python 环境。Hermes 管理的 venv 仍归 Hermes 所有;系统不会自动安装或恢复任何包。子进程环境会被净化,因此 cron 脚本不会继承 provider API 凭据和其他由 Hermes 管理的秘密。

Agent 为你设置这些​

cronjob 工具的 schema 直接向 Hermes 暴露了 no_agent,因此你可以在聊天中描述一个看门狗,让 agent 来配置它:

Ping me on Telegram if RAM is over 85%, every 5 minutes.

Hermes 会通过 write_file 将检查脚本写入 ~/.hermes/scripts/,然后调用:

cronjob(action="create", schedule="every 5m",
        script="memory-watchdog.sh", no_agent=True,
        deliver="telegram", name="memory-watchdog")

当消息内容完全由脚本决定时(看门狗、阈值告警、心跳),它会自动选择 no_agent=True。同一工具也让 agent 可以暂停、恢复、编辑和删除任务——整个生命周期都通过聊天驱动,无需任何人接触 CLI。

参见纯脚本 Cron 任务指南获取实际示例。

通过 context_from 串联任务​

Cron 任务在隔离的会话中运行,不保留之前运行的记忆。但有时一个任务的输出恰好是下一个任务所需的输入。context_from 参数自动建立这种连接——任务 B 的 prompt 在运行时会将任务 A 的最新输出作为上下文前置。

# 任务 1:收集原始数据
cronjob(
    action="create",
    prompt="Fetch the top 10 AI/ML stories from Hacker News. Save them to ~/.hermes/data/briefs/raw.md in markdown format with title, URL, and score.",
    schedule="0 7 * * *",
    name="AI News Collector",
)

# 任务 2:分类——接收任务 1 的输出作为上下文
# 从 cronjob(action="list") 获取任务 1 的 ID
cronjob(
    action="create",
    prompt="Read ~/.hermes/data/briefs/raw.md. Score each story 1–10 for engagement potential and novelty. Output the top 5 to ~/.hermes/data/briefs/ranked.md.",
    schedule="30 7 * * *",
    context_from="<job1_id>",
    name="AI News Triage",
)

# 任务 3:发布——接收任务 2 的输出作为上下文
cronjob(
    action="create",
    prompt="Read ~/.hermes/data/briefs/ranked.md. Write 3 tweet drafts (hook + body + hashtags). Deliver to telegram:7976161601.",
    schedule="0 8 * * *",
    context_from="<job2_id>",
    name="AI News Brief",
)

工作原理:

  • 任务 2 触发时,Hermes 从 ~/.hermes/cron/output/{job1_id}/*.md 读取任务 1 的最新输出
  • 该输出自动前置到任务 2 的 prompt
  • 任务 2 无需硬编码"读取此文件"——它以上下文形式接收内容
  • 链可以是任意长度:任务 1 → 任务 2 → 任务 3 → …

context_from 接受的格式:

| | 格式 | 示例

| 单个任务 ID(字符串) | context_from="a1b2c3d4"

| 多个任务 ID(列表) | context_from=["job_a", "job_b"]

输出按列表顺序拼接。

适用场景:

  • 多阶段流水线(收集 → 过滤 → 格式化 → 投递)
  • 步骤 N 依赖步骤 N−1 输出的依赖任务
  • 一个任务聚合多个其他任务结果的扇入模式

Provider 恢复​

Cron 任务继承你配置的回退 provider 和凭证池轮换。如果主 API key 被限速或 provider 返回错误,cron agent 可以:

  • 回退到备用 provider,前提是你在 config.yaml 中配置了 fallback_providers(或旧版 fallback_model)
  • 轮换到下一个凭证,即同一 provider 的凭证池中的下一个

这意味着高频运行或在高峰时段运行的 cron 任务更具弹性——单个被限速的 key 不会导致整次运行失败。

调度格式​

Agent 的最终响应会自动投递——你无需在 cron prompt 中为同一目标包含 send_message。如果 cron 运行调用了 send_message 且目标与调度器已投递的目标完全相同,Hermes 会跳过该重复发送,并告知模型将面向用户的内容放在最终响应中。仅对额外或不同的目标使用 send_message。

相对延迟(一次性)​

30m     → 30 分钟后运行一次
2h      → 2 小时后运行一次
1d      → 1 天后运行一次

间隔(周期性)​

every 30m    → 每 30 分钟
every 2h     → 每 2 小时
every 1d     → 每天

Cron 表达式​

0 9 * * *       → 每天上午 9:00
0 9 * * 1-5     → 工作日上午 9:00
0 */6 * * *     → 每 6 小时
30 8 1 * *      → 每月 1 日上午 8:30
0 0 * * 0       → 每周日午夜

ISO 时间戳​

2026-03-15T09:00:00    → 2026 年 3 月 15 日上午 9:00 一次性运行

重复行为​

| | 调度类型 | 默认重复次数 | 行为

| 一次性(30m、时间戳) | 1 | 运行一次

| 间隔(every 2h) | 永久 | 运行直到删除

| Cron 表达式 | 永久 | 运行直到删除

可以覆盖:

cronjob(
    action="create",
    prompt="...",
    schedule="every 2h",
    repeat=5,
)

以编程方式管理任务​

面向 agent 的 API 是单一工具:

cronjob(action="create", ...)
cronjob(action="list")
cronjob(action="update", job_id="...")
cronjob(action="pause", job_id="...")
cronjob(action="resume", job_id="...")
cronjob(action="run", job_id="...")
cronjob(action="remove", job_id="...")

对于 update,传入 skills=[] 可删除所有已附加的 skill。

Cron 任务可用的工具集​

Cron 在全新的 agent 会话中运行每个任务,不附加任何聊天平台。默认情况下,cron agent 获得你在 hermes tools 中为 cron 平台配置的工具集——不是 CLI 默认值,也不是所有工具。

hermes tools
# → 在 curses UI 中选择 "cron" 平台
# → 像 Telegram/Discord 等平台一样切换工具集开关

通过 cronjob.create(或通过 cronjob.update 对现有任务)上的 enabled_toolsets 字段可进行更精细的单任务控制:

cronjob(action="create", name="weekly-news-summary",
        schedule="every sunday 9am",
        enabled_toolsets=["web", "file"],      # 仅 web + file,无 terminal/browser 等
        prompt="Summarize this week's AI news: ...")

当任务上设置了 enabled_toolsets 时,它优先生效;否则 hermes tools 的 cron 平台配置生效;否则 Hermes 回退到内置默认值。这对成本控制很重要:在每个小型"获取新闻"任务中携带 moa、browser、delegation 会在每次 LLM 调用时膨胀工具 schema prompt。

完全跳过 agent:wakeAgent​

如果你的 cron 任务附加了预检脚本(通过 script=),脚本可以在运行时决定 Hermes 是否应该调用 agent。在 stdout 最后一行输出如下格式:

{"wakeAgent": false}

……cron 将完全跳过本次 tick 的 agent 运行。适用于高频轮询(每 1–5 分钟),只在状态实际发生变化时才需要唤醒 LLM——否则你会为一遍遍的零内容 agent 轮次付费。

# 预检脚本
import json, sys
latest = fetch_latest_issue_count()
prev = read_state("issue_count")
if latest == prev:
    print(json.dumps({"wakeAgent": False}))   # 跳过本次 tick
    sys.exit(0)
write_state("issue_count", latest)
print(json.dumps({"wakeAgent": True, "context": {"new_issues": latest - prev}}))

省略 wakeAgent 时,默认为 true(照常唤醒 agent)。

实用方案:低成本预运行门控​

wakeAgent 门控提供了一种零成本的方式,用于决定定时任务是否应该消耗任何 LLM token。三种模式覆盖了大多数使用场景。

文件变更门控——仅在被监视文件自上次成功 tick 以来有新内容时运行。调度器记录每个任务的 last_run_at;将其与文件的 mtime 比较。

#!/usr/bin/env bash
# ~/.hermes/scripts/feed-changed.sh
FEED="$HOME/data/feed.json"
STATE="$HOME/.hermes/scripts/.feed-changed.last"
test -f "$FEED" || { echo '{"wakeAgent": false}'; exit 0; }
mtime=$(stat -c %Y "$FEED")
last=$(cat "$STATE" 2>/dev/null || echo 0)
if [ "$mtime" -le "$last" ]; then
  echo '{"wakeAgent": false}'
else
  echo "$mtime" > "$STATE"
  echo '{"wakeAgent": true}'
fi
cronjob(action="create", name="process-feed",
        schedule="every 30m",
        script="feed-changed.sh",
        prompt="A new ~/data/feed.json has landed. Summarize what changed.")

外部标志门控——仅在其他进程发出就绪信号时运行(例如,部署 hook 落下一个文件,CI 任务在状态存储中设置一个值)。

#!/usr/bin/env bash
# ~/.hermes/scripts/flag-ready.sh
if test -f /tmp/new-data-ready; then
  rm -f /tmp/new-data-ready
  echo '{"wakeAgent": true}'
else
  echo '{"wakeAgent": false}'
fi
cronjob(action="create", name="nightly-analysis",
        schedule="0 9 * * *",
        script="flag-ready.sh",
        prompt="Run the nightly analysis over today's batch.")

SQL 计数门控——仅在你自己的数据库中有新行需要处理时运行。脚本还可以通过 context 将计数传递给 agent,让 agent 无需重新查询就知道数据量。

#!/usr/bin/env python
# ~/.hermes/scripts/new-rows.py
import json, sqlite3
conn = sqlite3.connect("/home/me/data/app.db")
n = conn.execute(
    "SELECT COUNT(*) FROM messages WHERE ts > strftime('%s','now','-2 hours')"
).fetchone()[0]
if n < 1:
    print(json.dumps({"wakeAgent": False}))
else:
    print(json.dumps({"wakeAgent": True, "context": {"new_rows": n}}))
cronjob(action="create", name="summarize-new-msgs",
        schedule="every 2h",
        script="new-rows.py",
        prompt="Summarize the new messages from the last 2 hours.")

同样的模式适用于任何可以从脚本查询的数据源——Postgres、HTTP API、你自己的状态存储——无需将 SQL 求值器内置到 cron 子系统中。

提示

Hermes 自身的 ~/.hermes/state.db 是内部 schema,会在版本间变更。不要从预运行门控中查询它——指向你自己的数据库或 feed。

致谢:此方案集由 @iankar8 在 #2654 中的探索所启发,该 PR 提议将 sql/file/command 触发器作为并行机制添加。script + wakeAgent 门控已以零成本覆盖了所有三种情况,因此该工作以文档形式落地。

串联任务:context_from​

Cron 任务可以通过在 context_from 中列出其他任务的名称(或 ID)来消费这些任务最近一次成功运行的输出:

cronjob(action="create", name="daily-digest",
        schedule="every day 7am",
        context_from=["ai-news-fetch", "github-prs-fetch"],
        prompt="Write the daily digest using the outputs above.")

被引用任务最近一次完成的输出会作为上下文注入到本次运行的 prompt 之上。每个上游条目必须是有效的任务 ID 或名称(参见 cronjob action="list")。注意:串联读取的是最近一次完成的输出——它不会等待同一 tick 中正在运行的上游任务。

任务存储​

任务存储在 ~/.hermes/cron/jobs.json。任务运行的输出保存到 ~/.hermes/cron/output/{job_id}/{timestamp}.md。

如果手动编辑使 jobs.json 格式出错,调度器会在下次加载时修复它,而不是停止运行:jobs 列表中不是 JSON 对象的条目会被丢弃,不是非负整数的 repeat.completed 会被规范化为非负整数(无法解析时为 0)。每次修复都会记录一条警告(只记录值的类型,不记录内容)。

任务可能将 model 和 provider 存储为 null。省略这些字段时,Hermes 在执行时从全局配置中解析它们。只有设置了单任务覆盖时,这些字段才会出现在任务记录中。

单任务的 base_url 覆盖必须同时指定 provider。对于保存了密钥的 provider(具名自定义 provider 或内置 provider),覆盖地址必须与该 provider 配置的端点同源:协议、主机和端口都相同。其他协议、端口或子域名都会被拒绝,因此保存的密钥只会发往你配置的地址。单独的 provider: custom 可以使用任何不会携带保存密钥的 base_url。如果某个保存的密钥与该 URL 的主机名匹配(例如 api.deepseek.com 对应 DEEPSEEK_API_KEY),则适用同样的规则:base_url 必须与你配置的端点或内置 provider 的端点同源。

存储使用原子文件写入,因此中断的写入不会留下部分写入的任务文件。

自包含的 prompt 仍然重要​

重要

Cron 任务在完全全新的 agent 会话中运行。Prompt 必须包含 agent 所需的一切,除非已由附加的 skill 提供。

错误: "Check on that server issue"

正确: "SSH into server 192.168.1.100 as user 'deploy', check if nginx is running with 'systemctl status nginx', and verify https://example.com returns HTTP 200."

安全性​

定时任务的 prompt 在创建和更新时会扫描 prompt 注入和凭证外泄模式。包含不可见 Unicode 技巧、SSH 后门尝试或明显的密钥外泄载荷的 prompt 会被拦截。

子智能体委派

delegate_task 工具会生成具有隔离上下文、继承工具访问权限和独立终端会话的子 AIAgent 实例。每个子智能体获得全新的对话并独立运行——只有其最终摘要会进入父智能体的上下文。

顶层模型调用会自动在后台运行。Hermes 会立即返回句柄,使对话可以继续,并在任务完成后将结果作为新消息发送回来。编排者子智能体会等待自己的工作线程完成,以便在返回前综合结果。

后台进程的生命周期​

后台终端进程属于启动它的智能体。委派结束并关闭子智能体时,系统会终止它仍在运行的进程,包括先前轮次启动的任务,但不会停止父智能体或其他子智能体拥有的进程。共享终端环境并不意味着共享进程所有权。

子智能体应等待构建、测试等有明确结束条件的后台命令完成,再返回最终摘要。如果 CI 监视器或服务器需要在子智能体结束后继续运行,应由父会话启动;返回进程 ID 不会将所有权转移给父智能体。

单任务​

delegate_task(
    goal="Debug why tests fail",
    context="Error: assertion in test_foo.py line 42"
)

并行批处理​

默认最多 3 个并发子智能体(可配置,无硬性上限):

delegate_task(tasks=[
    {"goal": "Research topic A", "context": "Focus on recent primary sources"},
    {"goal": "Research topic B", "context": "Compare the leading explanations"},
    {"goal": "Fix the build", "context": "Project root: /home/user/project"}
])

子智能体上下文的工作方式​

关键:子智能体一无所知

子智能体以全新对话启动。它们对父智能体的对话历史、之前的工具调用或委派前讨论的任何内容一无所知。子智能体的唯一上下文来自父智能体调用 delegate_task 时填写的 goal 和 context 字段。

唯一例外:当父智能体有已解析的工作区目录时,每个子智能体的系统提示都会嵌入该工作区的项目上下文文件(.hermes.md > AGENTS.md 链 > CLAUDE.md > .cursorrules——与主智能体系统提示相同的发现逻辑、优先级和大小上限;SOUL.md 除外)。在仓库中工作的子智能体无需重新发现即可遵循仓库自身的约定。

这意味着父智能体必须在调用中传递子智能体所需的一切信息:

# BAD - subagent has no idea what "the error" is
delegate_task(goal="Fix the error")

# GOOD - subagent has all context it needs
delegate_task(
    goal="Fix the TypeError in api/handlers.py",
    context="""The file api/handlers.py has a TypeError on line 47:
    'NoneType' object has no attribute 'get'.
    The function process_request() receives a dict from parse_body(),
    but parse_body() returns None when Content-Type is missing.
    The project is at /home/user/myproject and uses Python 3.11."""
)

子智能体会收到一个基于你的 goal 和 context 构建的专注系统 prompt(提示词),指示其完成任务并提供结构化摘要,包括所做的事情、发现的内容、修改的文件以及遇到的问题。

实际示例​

并行研究​

同时研究多个主题并收集摘要:

delegate_task(tasks=[
    {
        "goal": "Research the current state of WebAssembly in 2025",
        "context": "Focus on: browser support, non-browser runtimes, language support"
    },
    {
        "goal": "Research the current state of RISC-V adoption in 2025",
        "context": "Focus on: server chips, embedded systems, software ecosystem"
    },
    {
        "goal": "Research quantum computing progress in 2025",
        "context": "Focus on: error correction breakthroughs, practical applications, key players"
    }
])

代码审查 + 修复​

将审查并修复的工作流委派给全新上下文:

delegate_task(
    goal="Review the authentication module for security issues and fix any found",
    context="""Project at /home/user/webapp.
    Auth module files: src/auth/login.py, src/auth/jwt.py, src/auth/middleware.py.
    The project uses Flask, PyJWT, and bcrypt.
    Focus on: SQL injection, JWT validation, password handling, session management.
    Fix any issues found and run the test suite (pytest tests/auth/)."""
)

多文件重构​

将会大量占用父智能体上下文的大型重构任务委派出去:

delegate_task(
    goal="Refactor all Python files in src/ to replace print() with proper logging",
    context="""Project at /home/user/myproject.
    Use the 'logging' module with logger = logging.getLogger(__name__).
    Replace print() calls with appropriate log levels:
    - print(f"Error: ...") -> logger.error(...)
    - print(f"Warning: ...") -> logger.warning(...)
    - print(f"Debug: ...") -> logger.debug(...)
    - Other prints -> logger.info(...)
    Don't change print() in test files or CLI output.
    Run pytest after to verify nothing broke."""
)

批处理模式详情​

当顶层智能体提供 tasks 数组时,Hermes 会返回一个后台句柄,并行运行所有子智能体,并在每个子智能体完成后发送一条汇总结果。编排者子智能体则会在当前轮次中等待批处理完成,以便综合结果。

  • 最大并发数: 默认 3 个任务(可通过 delegation.max_concurrent_children 或环境变量 DELEGATION_MAX_CONCURRENT_CHILDREN 配置;最低为 1,无硬性上限)。超出限制的批次会返回工具错误,而不是被静默截断。
  • 线程池: 使用 ThreadPoolExecutor,以配置的并发限制作为最大工作线程数
  • 进度显示: 在 CLI 模式下,树形视图会实时显示每个子智能体的工具调用,并附带每个任务的完成行。在 gateway 模式下,进度会被批量汇总并转发给父智能体的进度回调
  • 结果排序: 结果按任务索引排序,与输入顺序一致,不受完成顺序影响
  • 取消: 后续消息不会取消顶层后台批处理。/stop 或关闭/重置所属会话会取消其活跃子智能体。同步编排者的子智能体仍会跟随其父智能体的中断状态

编排者发起的同步单任务委派会直接运行,不会产生线程池开销。

持久化后台完成事件​

后台委派完成后,Hermes 会先把完成事件写入当前 profile 的 state.db,再发布到正常的新轮次队列。如果 Hermes 在完成后、交付前重启,待处理事件会被恢复,并继续经过相同的所有权检查。多个消费者通过持久化 claim 竞争;只有成功接收合成轮次的消费者会确认交付,失败尝试会释放 claim 以便重试。

这不会在崩溃后恢复子智能体执行。如果委派仍在运行时其所有者进程消失,Hermes 会将其记录为 unknown,因为无法证明外部副作用是否已经发生。待处理和已交付记录都有界,并按 profile 隔离。

模型覆盖​

你可以通过 config.yaml 为子智能体配置不同的模型——适用于将简单任务委派给更便宜/更快的模型:

# In ~/.hermes/config.yaml
delegation:
  model: "google/gemini-flash-2.0"    # Cheaper model for subagents
  provider: "openrouter"              # Optional: route subagents to a different provider

如果省略,子智能体将使用与父智能体相同的模型。

成本策略:前沿模型规划,低价模型执行​

将问题分解为规格清晰的子任务需要前沿模型级别的判断力;而执行一个已经带有明确目标、完整上下文和输出约定的子任务通常不需要。与此同时,token 消耗主要发生在子智能体身上——并行批量子智能体通常消耗一次运行中绝大多数的 token,因此成本真正落在 worker 模型上。将 delegation.model 固定为低价模型、同时主会话保持前沿模型,可以把规划质量留在最需要的地方,并在消耗量最大的地方削减开支:

# ~/.hermes/config.yaml
model:
  default: "your-frontier-model"     # 父智能体(规划者)保持前沿模型
delegation:
  model: "your-inexpensive-model"    # 所有 delegate_task 子智能体运行此模型
  provider: "openrouter"             # 可选:将子智能体路由到不同的提供商

解析顺序:delegation.base_url(直连端点)优先,其次是 delegation.provider(通过运行时提供商系统解析完整凭证包);两者都未设置时,子智能体继承父智能体的提供商和凭证。delegation.model 在所有情况下生效,为空时子智能体继承父智能体的模型。

注意此固定是全局的:delegate_task 没有按任务指定模型的参数,批处理中的每个子智能体都运行配置的委派模型。对于需要更强模型的质量敏感型子任务,可以在该会话中不设置 delegation.model,或者将任务交给看板——看板支持按任务覆盖模型。

/review 命令​

/review 会派生一个独立的、拥有完整工具权限的后台评审子智能体,专门评审对话刚刚产出的工作——PR、diff、代码、文档、设计。它在所有界面均可用:CLI、TUI、桌面应用以及所有网关消息平台。

/review                       # 评审最近 10 条消息中呈现的工作
/review 重点关注安全性          # 为评审者附加额外指示

工作流程:

  • 最近 10 条用户/助手消息被快照为评审者的起始证据(工具输出和系统消息被排除)。
  • 评审子智能体在与 delegate_task 相同的后台委派通道上派发——它拥有完整的常规子智能体工具集(终端、网络、文件、浏览器等),因此会实际打开 PR、阅读 diff、运行代码,而不是仅凭摘录下判断。
  • 评审者继承主智能体的工作上下文:主智能体已加载的技能(启动预加载或会话中通过 skill_view 加载)会列在其简报中,并指示其加载这些技能、以其约定为标准评判工作。与所有子智能体一样,其系统提示也会嵌入工作区的项目上下文文件(AGENTS.md / CLAUDE.md / .cursorrules)作为具有约束力的约定。
  • 完成后,完整评审作为常规后台子智能体完成事件重新进入同一会话——你的主智能体可以看到并据此行动(修复问题、推送后续提交、回复你)。

典型流程:主智能体开了一个 PR,你输入 /review,第二双眼睛在你继续工作的同时对其进行调查;评审结果回到聊天中,交给创建该 PR 的智能体。

评审模型​

默认情况下评审者运行在你的主模型上。要固定专用评审模型,请在 config.yaml 中设置 auxiliary.review:

auxiliary:
  review:
    provider: openrouter               # 或 nous、anthropic、直连 base_url 等
    model: anthropic/claude-opus-4.6   # 一个强力的评审模型

凭证解析方式与 delegation.provider 固定完全相同(完整运行时提供商凭证包:base_url、API 密钥、api_mode)。provider: auto 加空 model 表示"继承主智能体的模型"——这是默认值。

/review 与 /refine 刻意分开:/refine 评审对话本身以更新记忆和技能,/review 评审对话产出的工作成果。

继承的工具访问权限​

delegate_task 不接受面向模型的 toolsets 参数。每个子智能体都会继承父智能体已启用的工具集,因此模型无法授予子智能体父智能体本身没有的能力。如果委派任务需要其他能力,请在开始对话前配置父智能体的工具。

即使父智能体拥有某些工具,以下工具仍会对子智能体屏蔽:

  • delegation — 对叶子子智能体屏蔽(默认)。role="orchestrator" 的子智能体可保留,受 max_spawn_depth 约束——参见下方深度限制与嵌套编排。
  • clarify — 子智能体无法与用户交互
  • memory — 不可写入共享持久内存
  • code_execution — 子智能体应逐步推理
  • send_message — 无跨平台副作用(例如发送 Telegram 消息)

最大迭代次数​

每个子智能体都有迭代次数限制(默认:50),控制其可进行的工具调用轮次:

delegate_task(
    goal="Quick file check",
    context="Check if /etc/nginx/nginx.conf exists and print its first 10 lines",
    max_iterations=10  # Simple task, don't need many turns
)

子智能体超时​

默认情况下,子智能体没有挂钟超时限制。子智能体只会因其实际执行的操作而失败——API 错误、工具错误或达到迭代预算上限——而不会被委派层面的计时器终止。早期版本曾设有硬性上限(300 秒,后为 600 秒),但这会在任务执行过程中误杀正常工作的子智能体:深度代码审查、大规模研究分发以及慢速推理模型经常需要超过 10 分钟,而它们全程都在稳定推进。

真正卡死的子智能体仍会被检测到:当子智能体没有任何进展(无 API 调用、无工具启动)时,心跳陈旧度监控会停止刷新父智能体的活动状态,从而让网关的不活动超时机制对真正卡死的工作进程生效。

如果仍需要上限(例如对无人值守的 cron 驱动委派进行成本控制),可按安装实例选择启用:

delegation:
  child_timeout_seconds: 0     # 默认:0 = 无超时
  # child_timeout_seconds: 1800  # 选择启用的「无进展」上限(下限 30 秒)

正值限制的是无进展时间,而不是总运行时长:它是子智能体在完全没有进展(没有完成的 API 调用、没有工具切换、活动时钟没有跳动)的情况下可以持续的最长时间,超过后才被放弃。任何进展迹象都会重新开始计时,因此正在等待耗时数分钟的补全任务的子智能体不会因为「耗时长」被终止(过去正是这种情况丢掉了已完成的工作);而真正停止推进的子智能体仍会被捕获(且进行中的请求本身还受单次调用 stale 看门狗约束)。0 或负值表示禁用;无论是否配置,下述心跳陈旧度监控始终生效。

零调用超时时的诊断转储

在配置了硬性上限的情况下,如果子智能体在零次 API 调用的情况下超时(通常原因:provider 不可达、认证失败或工具 schema 被拒绝),delegate_task 会将结构化诊断信息写入 ~/.hermes/logs/subagent-timeout-<session>-<timestamp>.log,其中包含子智能体的配置快照、凭据解析追踪以及早期错误消息。比之前的静默超时行为更易于定位根因。

监控运行中的子智能体(/agents)​

TUI 提供 /agents 浮层(别名 /tasks),将递归 delegate_task 扇出转化为一级审计界面:

  • 运行中和最近完成的子智能体的实时树形视图,按父智能体分组
  • 每个分支的费用、token 和已触及文件的汇总
  • 终止和暂停控制——可在不中断其兄弟智能体的情况下取消特定子智能体
  • 事后回顾:即使子智能体已返回父智能体,也可逐轮查看其历史记录

经典 CLI、TUI 和 Desktop 会在输入框上方自动显示正在运行的子智能体,包括总数、任务名称、已运行时间和最近活动。终端根据屏幕高度限制可见行数,并显示隐藏数量;Desktop 最多预览三个工作者。

  • 经典 CLI:F6 打开全屏实时列表;方向键选择,Enter 查看近期日志,PgUp/PgDn 滚动,s 输入引导,x 后按 y 确认停止。关闭后保留原有输入草稿。
  • TUI:Ctrl+T 或 /agents 打开完整树状列表;Enter/t 查看实时日志,d 查看详情(历史回放中 Enter 仍打开详情),e 输入引导,x 停止选中的工作者,X 停止其子树。
  • Desktop: 展开输入框上方的 Subagents,选择工作者查看详情并使用 Steer / Stop。

引导的“已排队”确认不代表子智能体已经读取;它会在检查点接收。日志预览只包含有大小限制的近期内容。工作者结束后离开实时列表,完成消息和已有历史视图仍可用于回顾。

在经典 CLI 和 TUI 中按 F7,可将实时栏折叠为单行摘要,再按一次恢复多行预览。单行保留运行数量和展开/恢复提示,空间允许时显示活动。输入和发送不受影响;关闭监视器后保留草稿及光标位置。此选项不写入配置。

经典 CLI 的 /agents 和 /tasks 仍打印文本摘要;父智能体忙碌时可直接按 F6 打开交互式监视器。参见 TUI — 斜杠命令。

深度限制与嵌套编排​

默认情况下,委派是扁平的:父智能体(深度 0)生成子智能体(深度 1),而这些子智能体无法进一步委派。这可防止失控的递归委派。

对于多阶段工作流(研究 → 综合,或对子问题进行并行编排),父智能体可以生成编排者子智能体,这些子智能体可以委派自己的工作线程:

delegate_task(
    goal="Survey three code review approaches and recommend one",
    role="orchestrator",  # Allows this child to spawn its own workers
    context="...",
)
  • role="leaf"(默认):子智能体无法进一步委派——与扁平委派行为相同。
  • role="orchestrator":子智能体保留 delegation 工具集。受 delegation.max_spawn_depth 约束(默认 1 = 扁平,因此在默认设置下 role="orchestrator" 无效)。将 max_spawn_depth 提高到 2 可允许编排者子智能体生成叶子孙智能体;设为 3 则允许三层(上限)。
  • delegation.orchestrator_enabled: false:全局开关,无论 role 参数如何,强制所有子智能体为 leaf。

费用警告: 在 max_spawn_depth: 3 和 max_concurrent_children: 3 的情况下,树可达到 3×3×3 = 27 个并发叶子智能体。每增加一层都会成倍增加开销——请谨慎提高 max_spawn_depth。

生命周期与持久性​

后台完成事件持久化并不等于执行持久化

在会话支持稍后交付结果时,顶层面向模型的 delegate_task 调用会自动在后台运行。Hermes 会立即返回句柄,并在子智能体或批处理完成后将结果重新发送到对话中。编排者子智能体会在当前轮次中等待自己的工作线程,因为它们必须在返回前综合这些结果。无法稍后交付分离结果的无状态请求/响应端点会回退到同步执行。

  • 普通后续消息不会取消后台子智能体。/stop 会取消运行中的后台委派,关闭或重置所属会话会丢弃其活跃子智能体。
  • 显式关闭或重置会话会中断该会话的后台子智能体。关闭由 TUI 查看、但由网关拥有的会话不会终止网关自己的后台工作。
  • Hermes 进程重启后不会恢复仍在运行的子智能体;该尝试会变为 unknown,因为 Hermes 无法证明哪些外部副作用已经发生。
  • 如果子智能体在重启前已经完成、但结果尚未交付,该完成事件会被恢复,并重新经过所属会话的正常路由检查。
  • 被取消的子智能体会返回结构化结果(status="interrupted",exit_reason="interrupted"),但由于父智能体也被中断,该结果通常不会出现在用户可见的回复中。

对于必须在会话关闭或进程重启后继续的持久执行,请使用:

  • cronjob(action=create)——调度独立的智能体运行;不受父智能体轮次中断影响。
  • terminal(background=True, notify_on_complete=True)——长时间运行的 shell 命令,在智能体执行其他操作时持续运行。

关键特性​

  • 每个子智能体获得其独立的终端会话(与父智能体分离)
  • 子智能体继承父智能体已启用的工具集;模型无法按调用选择或扩大这些工具集
  • 嵌套委派为可选项——只有 role="orchestrator" 的子智能体可以进一步委派,且仅在 max_spawn_depth 从默认值 1(扁平)提高后才生效。可通过 orchestrator_enabled: false 全局禁用。
  • 叶子子智能体不能调用:delegate_task、clarify、memory、send_message、execute_code。编排者子智能体保留 delegate_task,但仍不能使用其他四个。
  • 取消遵循所有权——/stop 或关闭/重置所属会话会取消其后台子智能体;编排者下的同步后代会跟随父智能体的中断状态
  • 只有最终摘要进入父智能体的上下文,保持 token 使用高效
  • 子智能体继承父智能体的 API 密钥、provider 配置和凭据池(支持在速率限制时轮换密钥)

delegate_task 与 execute_code 对比​

| | 因素 | delegate_task | execute_code

| 推理 | 完整 LLM 推理循环 | 仅 Python 代码执行

| 上下文 | 全新隔离对话 | 无对话,仅脚本

| 工具访问 | 所有非屏蔽工具,具备推理能力 | 通过 RPC 访问 7 个工具,无推理

| 并行性 | 默认 3 个并发子智能体(可配置) | 单脚本

| 最适合 | 需要判断力的复杂任务 | 机械式多步骤流水线

| Token 费用 | 较高(完整 LLM 循环) | 较低(仅返回 stdout)

| 用户交互 | 无(子智能体无法澄清) | 无

经验法则: 当子任务需要推理、判断或多步骤问题解决时,使用 delegate_task。当需要机械式数据处理或脚本化工作流时,使用 execute_code。

配置​

# In ~/.hermes/config.yaml
delegation:
  max_iterations: 50                        # Max turns per child (default: 50)
  # max_concurrent_children: 3              # Parallel children per batch (default: 3)
  # max_spawn_depth: 1                      # Tree depth (1-3, default 1 = flat). Raise to 2 to allow orchestrator children to spawn leaves; 3 for three levels.
  # orchestrator_enabled: true              # Disable to force all children to leaf role.
  model: "google/gemini-3-flash-preview"             # Optional provider/model override
  provider: "openrouter"                             # Optional built-in provider
  api_mode: anthropic_messages                       # optional; auto-detected from base_url for anthropic_messages endpoints

# Or use a direct custom endpoint instead of provider:
delegation:
  model: "qwen2.5-coder"
  base_url: "http://localhost:1234/v1"
  api_key: "local-key"
  # api_mode: "anthropic_messages"  # Optional. Wire protocol override for base_url ("chat_completions", "codex_responses", or "anthropic_messages"). Empty = auto-detect from URL (e.g. /anthropic suffix). Set explicitly for endpoints the heuristic can't classify (Azure AI Foundry, MiniMax, Zhipu GLM, LiteLLM proxies, …).

当 base_url 指向 Anthropic 兼容端点时——例如路径以 /anthropic 结尾、Azure Foundry Claude 路由或 MiniMax /anthropic 代理——api_mode 会被自动检测为 anthropic_messages,子智能体无需任何配置即可使用正确的传输格式。当自动检测结果有误时(罕见),请显式设置 api_mode。

提示

智能体会根据任务复杂度自动处理委派。你无需明确要求它进行委派——它会在合适时自行决定。

Plugins

Hermes 提供了一套插件系统,可在不修改核心代码的情况下添加自定义工具、hook(钩子)和集成。

如果你想为自己、团队或某个项目创建自定义工具,这通常是正确的路径。开发者指南中的

Adding Tools 页面针对的是存放在 tools/ 和 toolsets.py 中的 Hermes 内置核心工具。

→ 构建 Hermes Plugin — 包含完整可运行示例的分步指南。

快速概览​

在 ~/.hermes/plugins/ 下放入一个目录,包含 plugin.yaml 和 Python 代码:

~/.hermes/plugins/my-plugin/
├── plugin.yaml      # manifest(清单)
├── __init__.py      # register() — 将 schema 与处理器绑定
├── schemas.py       # tool schema(LLM 所见的内容)
└── tools.py         # tool 处理器(调用时实际执行的代码)

启动 Hermes — 你的工具会与内置工具一同出现,模型可立即调用它们。

最小可运行示例​

以下是一个完整插件,添加了一个 hello_world 工具,并通过 hook 记录每次工具调用。

~/.hermes/plugins/hello-world/plugin.yaml

name: hello-world
version: "1.0"
description: A minimal example plugin

~/.hermes/plugins/hello-world/__init__.py

"""Minimal Hermes plugin — registers a tool and a hook."""

import json


def register(ctx):
    # --- Tool: hello_world ---
    schema = {
        "name": "hello_world",
        "description": "Returns a friendly greeting for the given name.",
        "parameters": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "Name to greet",
                }
            },
            "required": ["name"],
        },
    }

    def handle_hello(params, **kwargs):
        del kwargs
        name = params.get("name", "World")
        return json.dumps({"success": True, "greeting": f"Hello, {name}!"})

    ctx.register_tool(
        name="hello_world",
        toolset="hello_world",
        schema=schema,
        handler=handle_hello,
    )

    # --- Hook: log every tool call ---
    def on_tool_call(tool_name, params, result):
        print(f"[hello-world] tool called: {tool_name}")

    ctx.register_hook("post_tool_call", on_tool_call)

将两个文件放入 ~/.hermes/plugins/hello-world/,重启 Hermes,模型即可立即调用 hello_world。每次工具调用后,hook 会打印一行日志。

面向模型的工具描述应写在 schema["description"] 中。可选的 ctx.register_tool(description=...) 值是独立的 ToolEntry 注册表元数据:省略时,它会默认使用 schema 中的描述;但如果 schema 缺少 description,Hermes 不会把该元数据反向复制到 schema。建议只在 schema 中定义一次描述。如果同时提供两个值,请保持同步;模型看到的是 schema 中的值。

./.hermes/plugins/ 下的项目本地插件默认禁用。仅对可信仓库启用,方法是在启动 Hermes 前设置 HERMES_ENABLE_PROJECT_PLUGINS=true。

插件能做什么​

以下所有 ctx.* API 均可在插件的 register(ctx) 函数中使用。

| | 能力 | 方式

| 添加工具 | ctx.register_tool(name=..., toolset=..., schema=..., handler=...)

| 添加 hook | ctx.register_hook("post_tool_call", callback)

| 添加斜杠命令 | ctx.register_command(name, handler, description) — 在 CLI 和 gateway 会话中添加 /name

| 从命令中调度工具 | ctx.dispatch_tool(name, args) — 调用已注册的工具,自动注入父 agent 上下文

| 添加 CLI 命令 | ctx.register_cli_command(name, help, setup_fn, handler_fn) — 添加 hermes <plugin> <subcommand>

| 注入消息 | ctx.inject_message(content, role="user") — 参见 注入消息

| 附带数据文件 | Path(__file__).parent / "data" / "file.yaml"

| 打包 skill | ctx.register_skill(name, path) — 命名空间为 plugin:skill,通过 skill_view("plugin:skill") 加载

| 按环境变量控制 | 在 plugin.yaml 中设置 requires_env: [API_KEY] — 在 hermes plugins install 时提示输入

| 通过 pip 分发 | [project.entry-points."hermes_agent.plugins"]

| 注册 gateway 平台(Discord、Telegram、IRC 等) | ctx.register_platform(name, label, adapter_factory, check_fn, ...) — 参见 Adding Platform Adapters

| 注册图像生成后端 | ctx.register_image_gen_provider(provider) — 参见 Image Generation Provider Plugins

| 注册视频生成后端 | ctx.register_video_gen_provider(provider) — 参见 Video Generation Provider Plugins

| 注册上下文压缩引擎 | ctx.register_context_engine(engine) — 参见 Context Engine Plugins

| 注册 memory 后端 | 在 plugins/memory/<name>/__init__.py 中继承 MemoryProvider — 参见 Memory Provider Plugins(使用独立发现系统)

| 调用宿主 LLM | ctx.llm.complete(...) / ctx.llm.complete_structured(...) — 借用用户当前激活的模型和认证,进行一次性补全,支持可选 JSON schema 验证。参见 Plugin LLM Access

| 注册推理后端(LLM provider) | 在 plugins/model-providers/<name>/__init__.py 中调用 register_provider(ProviderProfile(...)) — 参见 Model Provider Plugins(使用独立发现系统)

插件发现​

| | 来源 | 路径 | 使用场景

| 内置 | <repo>/plugins/ | 随 Hermes 附带 — 参见 Built-in Plugins

| 用户 | ~/.hermes/plugins/ | 个人插件

| 项目 | .hermes/plugins/ | 项目专属插件(需要 HERMES_ENABLE_PROJECT_PLUGINS=true)

| pip | hermes_agent.plugins entry_points | 分发包

| Nix | services.hermes-agent.extraPlugins / extraPythonPackages | NixOS 声明式安装 — 参见 Nix Setup

名称冲突时,后面的来源会覆盖前面的,因此与内置插件同名的用户插件会替换它。

插件子分类​

在每个来源内,Hermes 还识别将插件路由到专用发现系统的子分类目录:

| | 子目录 | 内容 | 发现系统

| plugins/(根目录) | 通用插件 — 工具、hook、斜杠命令、CLI 命令、打包 skill | PluginManager(kind: standalone 或 backend)

| plugins/platforms/<name>/ | Gateway 频道适配器(ctx.register_platform()) | PluginManager(kind: platform,深一层)

| plugins/image_gen/<name>/ | 图像生成后端(ctx.register_image_gen_provider()) | PluginManager(kind: backend,深一层)

| plugins/memory/<name>/ | Memory provider(继承 MemoryProvider) | 独立加载器,位于 plugins/memory/__init__.py(kind: exclusive — 同时只有一个激活)

| plugins/context_engine/<name>/ | 上下文压缩引擎(ctx.register_context_engine()) | 独立加载器,位于 plugins/context_engine/__init__.py(同时只有一个激活)

| plugins/model-providers/<name>/ | LLM provider profile(register_provider(ProviderProfile(...))) | 独立加载器,位于 providers/__init__.py(首次调用 get_provider_profile() 时懒加载扫描)

~/.hermes/plugins/model-providers/<name>/ 和 ~/.hermes/plugins/memory/<name>/ 下的用户插件会覆盖同名内置插件 — register_provider() / register_memory_provider() 中后写者胜出。放入一个目录即可替换内置实现,无需修改仓库。

子分类插件在 hermes plugins list 和交互式 hermes plugins UI 中以路径派生的 key 显示 — 例如 image_gen/openai、platforms/teams。该 key(而非 manifest 中的 name:)是传给 hermes plugins enable … / disable … 的值,也是在 config.yaml 的 plugins.enabled 下填写的字符串。

插件默认关闭(少数例外)​

通用插件和用户安装的后端默认禁用 — 发现系统会找到它们(因此它们会出现在 hermes plugins 和 /plugins 中),但在你将插件名称添加到 ~/.hermes/config.yaml 的 plugins.enabled 之前,任何带有 hook 或工具的内容都不会加载。这可防止第三方代码在未经明确同意的情况下运行。

plugins:
  enabled:
    - my-tool-plugin
    - disk-cleanup
  disabled:       # 可选的拒绝列表 — 若名称同时出现在两个列表中,此列表始终优先
    - noisy-plugin

切换状态的三种方式:

hermes plugins                    # 交互式切换(空格勾选/取消勾选)
hermes plugins enable <name>      # 添加到允许列表
hermes plugins disable <name>     # 从允许列表移除并添加到禁用列表

如果你保存过工具选择(hermes tools,即 platform_toolsets 显式列出工具集),禁用插件时也会把它的工具集从这些已保存的列表中移除,启用时再加回去。CLI 与 Desktop/TUI/仪表盘开关的行为一致。没有已保存的选择时不会改动列表:插件的工具集默认开启。

执行 hermes plugins install owner/repo 后,会询问 Enable 'name' now? [y/N] — 默认为否。脚本化安装时可用 --enable 或 --no-enable 跳过提示。

允许列表不控制的内容​

某些类别的插件绕过 plugins.enabled — 它们是 Hermes 内置功能的一部分,若默认关闭会破坏基本功能:

| | 插件类型 | 激活方式

| 内置平台插件(IRC、Teams 等,位于 plugins/platforms/) | 自动加载,使所有内置 gateway 频道可用。实际频道通过 config.yaml 中的 gateway.platforms.<name>.enabled 开启。

| 内置后端(plugins/image_gen/ 等下的图像生成 provider) | 自动加载,使默认后端"开箱即用"。通过 config.yaml 中的 <category>.provider 选择(例如 image_gen.provider: openai)。

| Memory provider(plugins/memory/) | 全部发现;同时只有一个激活,由 config.yaml 中的 memory.provider 选择。

| Context engine(plugins/context_engine/) | 全部发现;同时只有一个激活,由 config.yaml 中的 context.engine 选择。

| Model provider(plugins/model-providers/) | plugins/model-providers/ 下的所有内置 provider 在首次调用 get_provider_profile() 时发现并注册。用户通过 --provider 或 config.yaml 一次选择一个。

| pip 安装的 backend 插件 | 通过 plugins.enabled 选择加入(与通用插件相同)。

| 用户安装的平台(位于 ~/.hermes/plugins/platforms/) | 通过 plugins.enabled 选择加入 — 第三方 gateway 适配器需要明确同意。

简而言之:内置的"始终可用"基础设施自动加载;第三方通用插件需选择加入。 plugins.enabled 允许列表专门用于控制用户放入 ~/.hermes/plugins/ 的任意代码。

现有用户的迁移​

当你升级到支持选择加入插件的 Hermes 版本(config schema v21+)时,已安装在 ~/.hermes/plugins/ 下且不在 plugins.disabled 中的用户插件会自动纳入 plugins.enabled。你的现有配置继续正常工作。内置独立插件不会自动纳入 — 即使是现有用户也需要明确选择加入。(内置平台/后端插件从未需要纳入,因为它们从未被控制。)

可用 hook​

插件可注册 hermes_cli.plugins.VALID_HOOKS 当前接受的 24 个生命周期事件。**Event Hooks 目录**是精确触发时机、返回值处理、payload 字段和隐私说明的 canonical reference。

| | 描述性类别 | 已发布 hook

| 指令/控制 | pre_tool_call, pre_llm_call, pre_verify, pre_gateway_dispatch

| Transform | transform_tool_result, transform_terminal_output, transform_llm_output

| 观察者 | post_tool_call, post_llm_call, pre_api_request, post_api_request, api_request_error, on_session_start, on_session_end, on_session_finalize, on_session_reset, on_skill_lifecycle, subagent_start, subagent_stop, pre_approval_request, post_approval_response, kanban_task_claimed, kanban_task_completed, kanban_task_blocked

这些类别只描述当前行为,不规定未来命名规则。Plugin middleware 仍是独立的 registry/surface。

插件类型​

Hermes 有四种插件:

| | 类型 | 作用 | 选择方式 | 位置

| 通用插件 | 添加工具、hook、斜杠命令、CLI 命令 | 多选(启用/禁用) | ~/.hermes/plugins/

| Memory provider | 替换或增强内置 memory | 单选(同时只有一个激活) | plugins/memory/

| Context engine | 替换内置上下文压缩器 | 单选(同时只有一个激活) | plugins/context_engine/

| Model provider | 声明推理后端(OpenRouter、Anthropic 等) | 多注册,通过 --provider / config.yaml 选择 | plugins/model-providers/

Memory provider 和 context engine 是 provider 插件 — 每种类型同时只能有一个激活。Model provider 也是插件,但可以同时加载多个;用户通过 --provider 或 config.yaml 一次选择一个。通用插件可以任意组合启用。

可插拔接口 — 各场景对应文档​

上表展示了四种插件类别,但在"通用插件"中,PluginContext 暴露了多个不同的扩展点 — Hermes 还接受 Python 插件系统之外的扩展(配置驱动的后端、shell hook 命令、外部服务器等)。使用下表找到适合你需求的文档:

| | 想要添加… | 方式 | 编写指南

| LLM 可调用的工具 | Python 插件 — ctx.register_tool() | Build a Hermes Plugin · Adding Tools

| 生命周期 hook(LLM 前后、会话开始/结束、工具过滤) | Python 插件 — ctx.register_hook() | Hooks reference · Build a Hermes Plugin

| CLI / gateway 的斜杠命令 | Python 插件 — ctx.register_command() | Build a Hermes Plugin · Extending the CLI

| hermes <thing> 的子命令 | Python 插件 — ctx.register_cli_command() | Extending the CLI

| 插件附带的skill | Python 插件 — ctx.register_skill() | Creating Skills

| 推理后端(LLM provider:OpenAI 兼容、Codex、Anthropic-Messages、Bedrock) | Provider 插件 — 在 plugins/model-providers/<name>/ 中调用 register_provider(ProviderProfile(...)) | Model Provider Plugins · Adding Providers

| Gateway 频道(Discord / Telegram / IRC / Teams 等) | 平台插件 — 在 plugins/platforms/<name>/ 中调用 ctx.register_platform() | Adding Platform Adapters

| Memory 后端(Honcho、Mem0、Supermemory 等) | Memory 插件 — 在 plugins/memory/<name>/ 中继承 MemoryProvider | Memory Provider Plugins

| 上下文压缩策略 | Context-engine 插件 — ctx.register_context_engine() | Context Engine Plugins

| 图像生成后端(DALL·E、SDXL 等) | 后端插件 — ctx.register_image_gen_provider() | Image Generation Provider Plugins

| 视频生成后端(Veo、Kling、Pixverse、Grok-Imagine、Runway 等) | 后端插件 — ctx.register_video_gen_provider() | Video Generation Provider Plugins

| TTS 后端(任意 CLI — Piper、VoxCPM、Kokoro、xtts、语音克隆脚本等) | 配置驱动(推荐)— 在 config.yaml 的 tts.providers.<name> 下以 type: command 声明。或 Python 后端插件 — 对需要超出 shell 模板的 Python SDK / 流式引擎使用 ctx.register_tts_provider()。 | TTS Setup · Python plugin guide

| STT 后端(自定义 whisper 二进制、本地 ASR CLI) | 配置驱动 — 将 HERMES_LOCAL_STT_COMMAND 环境变量设置为 shell 模板 | Voice Message Transcription (STT)

| 通过 MCP 使用外部工具(文件系统、GitHub、Linear、Notion、任意 MCP 服务器) | 配置驱动 — 在 config.yaml 中以 command: / url: 声明 mcp_servers.<name>。Hermes 自动发现服务器的工具并与内置工具一同注册。 | MCP

| 额外 skill 来源(自定义 GitHub 仓库、私有 skill 索引) | CLI — hermes skills tap add <repo> | Skills Hub · 发布自定义 tap

| Gateway 事件 hook(在 gateway:startup、session:start、agent:end、command:* 时触发) | 将 HOOK.yaml + handler.py 放入 ~/.hermes/hooks/<name>/ | Event Hooks

| Shell hook(在事件时运行 shell 命令 — 通知、审计日志、桌面提醒) | 配置驱动 — 在 config.yaml 的 hooks: 下声明 | Shell Hooks

备注

并非所有扩展都是 Python 插件。某些扩展接口有意使用配置驱动的 shell 命令(TTS、STT、shell hook),这样你已有的任意 CLI 无需编写 Python 即可成为插件。其他的是 agent 连接并自动注册工具的外部服务器(MCP)。还有一些是拥有自己 manifest 格式的即插即用目录(gateway hook)。根据你的集成风格选择合适的接口;上表中的编写指南各自涵盖了占位符、发现机制和示例。

NixOS 声明式插件​

在 NixOS 上,插件可通过模块选项声明式安装 — 无需 hermes plugins install。完整详情请参见 Nix Setup 指南。

services.hermes-agent = {
  # 目录插件(包含 plugin.yaml 的源码树)
  extraPlugins = [ (pkgs.fetchFromGitHub { ... }) ];
  # 入口点插件(pip 包)
  extraPythonPackages = [ (config.services.hermes-agent.package.python.pkgs.buildPythonPackage { ... }) ];
  # 在 config 中启用
  settings.plugins.enabled = [ "my-plugin" ];
};

声明式插件以 nix-managed- 前缀符号链接 — 与手动安装的插件共存,从 Nix 配置中移除后自动清理。

管理插件​

hermes plugins                                       # 统一交互式 UI
hermes plugins list                                  # 表格:已启用 / 已禁用 / 未启用
hermes plugins install user/repo                     # 从 Git 安装,然后提示 Enable? [y/N]
hermes plugins install user/repo --enable            # 请求启用;依赖安装仍需单独同意
hermes plugins install user/repo --no-enable         # 安装但保持禁用(无提示)
hermes plugins update my-plugin                      # 拉取最新版本
hermes plugins remove my-plugin                      # 卸载
hermes plugins enable my-plugin                      # 添加到允许列表(普通插件)
hermes plugins enable image_gen/openai               # 添加到允许列表(子分类插件)
hermes plugins disable my-plugin                     # 从允许列表移除并添加到禁用列表

对于子分类目录下的插件(例如 plugins/image_gen/openai/、plugins/platforms/teams/),使用完整的 <category>/<plugin> key — 这正是 hermes plugins list 在 Name 列中显示的内容。

更新检查、来源与依赖​

hermes plugins check-updates
hermes plugins adopt my-plugin
hermes plugins trust-update-url my-plugin

Git 安装的来源和版本记录在 .install-metadata.json。

未固定版本的已跟踪插件比较保存的远程来源;固定版本保持固定。

自行克隆的目录需先 adopt 才纳入管理,手动复制或来源漂移的目录仅得到诊断提示。

pip 入口点插件可报告发行包版本,但不会因此转为 Git 管理。

Gateway 按 plugins.auto_update_check_hours 检查更新,默认 24 小时,0 关闭。

check-updates 不改写插件文件。检查记录可通过 hermes pm status 和桌面同步状态查看。

默认通过 hermes plugins update NAME 手动应用;plugins.auto_apply: true 才允许跟踪的 Git 插件自动更新。

固定版本、手动目录、来源漂移和 pip 发行包不参与自动应用。

新引入或变更的 update_url 需通过 trust-update-url 审阅确认。

Python 依赖安装有独立同意和准入步骤,--enable 不绕过它。

拒绝或非交互式依赖安装可使插件保留为已安装但未启用状态。

PM 统一准备核心和插件依赖,失败保留旧选择。新环境尚未进入当前进程时需重启。

普通应用更新保留插件目录,显式插件更新或删除可以改动这些文件。

详见包管理。

交互式 UI​

不带参数运行 hermes plugins 会打开一个复合交互界面:

Plugins
  ↑↓ navigate  SPACE toggle  ENTER configure/confirm  ESC done

  General Plugins
 → [✓] my-tool-plugin — Custom search tool
   [ ] webhook-notifier — Event hooks
   [ ] disk-cleanup — Auto-cleanup of ephemeral files [bundled]

  Provider Plugins
     Memory Provider          ▸ honcho
     Context Engine           ▸ compressor
  • General Plugins 区域 — 复选框,用空格切换。勾选 = 在 plugins.enabled 中,未勾选 = 在 plugins.disabled 中(明确关闭)。
  • Provider Plugins 区域 — 显示当前选择。按 ENTER 进入单选选择器,选择一个激活的 provider。
  • 内置插件在同一列表中显示,带有 [bundled] 标签。

Provider 插件的选择保存到 config.yaml:

memory:
  provider: "honcho"      # 空字符串 = 仅使用内置

context:
  engine: "compressor"    # 默认内置压缩器

已启用 vs. 已禁用 vs. 未设置​

插件处于以下三种状态之一:

| | 状态 | 含义 | 在 plugins.enabled 中? | 在 plugins.disabled 中?

| enabled | 下次会话时加载 | 是 | 否

| disabled | 明确关闭 — 即使同时在 enabled 中也不会加载 | (无关) | 是

| not enabled | 已发现但从未选择加入 | 否 | 否

新安装或内置插件的默认状态为 not enabled。hermes plugins list 显示全部三种状态,便于区分明确关闭的插件和等待启用的插件。

在运行中的会话里,/plugins 显示当前已加载的插件。

注入消息​

插件可使用 ctx.inject_message() 向活跃对话注入消息:

ctx.inject_message("New data arrived from the webhook", role="user")

签名: ctx.inject_message(content: str, role: str = "user") -> bool

工作原理:

  • 若 agent 空闲(等待用户输入),消息会作为下一条输入排队并开始新一轮。
  • 若 agent 处于轮次中(正在运行),消息会中断当前操作 — 与用户输入新消息并按下 Enter 效果相同。
  • 对于非 "user" 角色,内容会以 [role] 为前缀(例如 [system] ...)。
  • 若消息成功排队返回 True,若无 CLI 引用(例如在 gateway 模式下)则返回 False。

这使得远程控制查看器、消息桥接或 webhook 接收器等插件能够从外部来源向对话注入消息。

备注

inject_message 仅在 CLI 模式下可用。在 gateway 模式下,没有 CLI 引用,该方法返回 False。

完整的处理器约定、schema 格式、hook 行为、错误处理和常见错误请参见 完整指南。

MCP(模型上下文协议)

本页的 Python 依赖命令使用 PM 准备的源码环境。

依赖变更后,请重新激活该 checkout 并重启 Hermes。

MCP 让 Hermes Agent 连接到外部工具服务器,使 agent 能够使用 Hermes 本身之外的工具——GitHub、数据库、文件系统、浏览器栈、内部 API 等等。

如果你曾经希望 Hermes 使用某个已经存在于其他地方的工具,MCP 通常是最简洁的方式。

MCP 能给你带来什么​

  • 无需先编写原生 Hermes 工具,即可访问外部工具生态系统
  • 在同一配置中同时支持本地 stdio 服务器和远程 HTTP MCP 服务器
  • 启动时自动发现并注册工具
  • 在服务器支持的情况下,提供针对 MCP 资源和 prompt(提示词)的实用工具封装
  • 按服务器过滤,只向 Hermes 暴露你真正需要的 MCP 工具

快速开始​

  • 安装 MCP 支持(如果你使用了标准安装脚本,已包含在内):
cd ~/.hermes/hermes-agent
python -c "import pm; pm.sync_venv(['mcp'], explicit=True)"
  • 在 ~/.hermes/config.yaml 中添加一个 MCP 服务器:
mcp_servers:
  filesystem:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
  • 启动 Hermes:
hermes chat
  • 让 Hermes 使用 MCP 支持的能力。

例如:

List the files in /home/user/projects and summarize the repo structure.

Hermes 会发现 MCP 服务器的工具,并像使用其他工具一样使用它们。

两种 MCP 服务器​

Stdio 服务器​

Stdio 服务器作为本地子进程运行,通过 stdin/stdout 通信。

mcp_servers:
  github:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    env:
      GITHUB_PERSONAL_ACCESS_TOKEN: "***"

适合使用 stdio 服务器的场景:

  • 服务器已在本地安装
  • 需要低延迟访问本地资源
  • 你参考的 MCP 服务器文档中使用了 command、args 和 env

HTTP 服务器​

HTTP MCP 服务器是 Hermes 直接连接的远程端点。

mcp_servers:
  remote_api:
    url: "https://mcp.example.com/mcp"
    headers:
      Authorization: "Bearer ***"

适合使用 HTTP 服务器的场景:

  • MCP 服务器托管在其他地方
  • 你的组织暴露了内部 MCP 端点
  • 你不希望 Hermes 为该集成在本地启动子进程

基本配置参考​

Hermes 从 ~/.hermes/config.yaml 的 mcp_servers 下读取 MCP 配置。

常用字段​

| | 字段 | 类型 | 含义

| command | string | stdio MCP 服务器的可执行文件

| args | list | stdio 服务器的参数

| env | mapping | 传递给 stdio 服务器的环境变量

| url | string | HTTP MCP 端点

| headers | mapping | 远程服务器的 HTTP 头

| timeout | number | 工具调用超时时间

| connect_timeout | number | 初始连接超时时间

| enabled | bool | 若为 false,Hermes 完全跳过该服务器

| supports_parallel_tool_calls | bool | 若为 true,该服务器的工具可并发运行

| tools | mapping | 按服务器过滤工具及实用工具策略

最简 stdio 示例​

mcp_servers:
  filesystem:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]

最简 HTTP 示例​

mcp_servers:
  company_api:
    url: "https://mcp.internal.example.com"
    headers:
      Authorization: "Bearer ***"

内置预设​

对于知名 MCP 服务器,hermes mcp add 接受 --preset 标志,自动填写传输层细节,无需手动查找命令和参数。预设只提供默认值——你在同一命令行传入的其他内容(环境变量、头信息、过滤规则)仍然优先生效。

| | 预设 | 配置内容

| codex | Codex CLI 的 MCP 服务器(通过 stdio 运行 codex mcp-server)。需要 PATH 中存在 codex CLI。

# 一行命令将 Codex CLI 添加为 MCP 服务器
hermes mcp add codex --preset codex

等价于写入:

mcp_servers:
  codex:
    command: "codex"
    args: ["mcp-server"]

你可以使用任意本地名称(hermes mcp add my-codex --preset codex 完全可以);预设只提供 command/args 默认值。

Hermes 注册 MCP 工具的方式​

Hermes 为 MCP 工具添加前缀,避免与内置名称冲突:

mcp_<server_name>_<tool_name>

示例:

| | 服务器 | MCP 工具 | 注册名称

| filesystem | read_file | mcp_filesystem_read_file

| github | create-issue | mcp_github_create_issue

| my-api | query.data | mcp_my_api_query_data

实际使用中,你通常不需要手动调用带前缀的名称——Hermes 在正常推理过程中会自动识别并选择该工具。

MCP 实用工具​

在服务器支持的情况下,Hermes 还会围绕 MCP 资源和 prompt 注册实用工具:

  • list_resources
  • read_resource
  • list_prompts
  • get_prompt

这些工具按服务器注册,遵循相同的前缀规则,例如:

  • mcp_github_list_resources
  • mcp_github_get_prompt

重要说明​

这些实用工具现在具备能力感知:

  • 只有当 MCP 会话实际支持资源操作时,Hermes 才注册资源实用工具
  • 只有当 MCP 会话实际支持 prompt 操作时,Hermes 才注册 prompt 实用工具

因此,一个只暴露可调用工具而没有资源/prompt 的服务器,不会获得这些额外的封装。

按服务器过滤​

你可以控制每个 MCP 服务器向 Hermes 贡献哪些工具,从而精细管理工具命名空间。

完全禁用某个服务器​

mcp_servers:
  legacy:
    url: "https://mcp.legacy.internal"
    enabled: false

若 enabled: false,Hermes 完全跳过该服务器,甚至不尝试连接。

白名单过滤服务器工具​

mcp_servers:
  github:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    env:
      GITHUB_PERSONAL_ACCESS_TOKEN: "***"
    tools:
      include: [create_issue, list_issues]

只有列出的 MCP 服务器工具会被注册。

黑名单过滤服务器工具​

mcp_servers:
  stripe:
    url: "https://mcp.stripe.com"
    tools:
      exclude: [delete_customer]

除排除项外,所有服务器工具均被注册。

优先级规则​

若两者同时存在:

tools:
  include: [create_issue]
  exclude: [create_issue, delete_issue]

include 优先生效。

同样可过滤实用工具​

你也可以单独禁用 Hermes 添加的实用工具封装:

mcp_servers:
  docs:
    url: "https://mcp.docs.example.com"
    tools:
      prompts: false
      resources: false

含义:

  • tools.resources: false 禁用 list_resources 和 read_resource
  • tools.prompts: false 禁用 list_prompts 和 get_prompt

完整示例​

mcp_servers:
  github:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    env:
      GITHUB_PERSONAL_ACCESS_TOKEN: "***"
    tools:
      include: [create_issue, list_issues, search_code]
      prompts: false

  stripe:
    url: "https://mcp.stripe.com"
    headers:
      Authorization: "Bearer ***"
    tools:
      exclude: [delete_customer]
      resources: false

  legacy:
    url: "https://mcp.legacy.internal"
    enabled: false

如果所有工具都被过滤掉会怎样?​

如果你的配置过滤掉了所有可调用工具,并禁用或省略了所有支持的实用工具,Hermes 不会为该服务器创建空的运行时 MCP 工具集。

这样可以保持工具列表整洁。

运行时行为​

发现时机​

Hermes 在启动时发现 MCP 服务器,并将其工具注册到普通工具注册表中。

动态工具发现​

MCP 服务器可以在运行时通过发送 notifications/tools/list_changed 通知,告知 Hermes 其可用工具发生了变化。Hermes 收到该通知后,会自动重新获取服务器的工具列表并更新注册表——无需手动执行 /reload-mcp。

这对于能力动态变化的 MCP 服务器非常有用(例如,加载新数据库 schema 时添加工具,或服务下线时移除工具)。

刷新操作受锁保护,因此同一服务器快速连续发送的通知不会导致重叠刷新。prompt 和资源变更通知(prompts/list_changed、resources/list_changed)会被接收,但暂未处理。

重新加载​

如果你修改了 MCP 配置,请使用:

/reload-mcp

这会从配置重新加载 MCP 服务器并刷新可用工具列表。对于服务器主动推送的运行时工具变更,请参阅上方的动态工具发现。

工具集​

每个已配置的 MCP 服务器,在贡献至少一个已注册工具时,也会创建一个运行时工具集:

mcp-<server>

这使得在工具集层面更容易理解 MCP 服务器的情况。

安全模型​

Stdio 环境变量过滤​

对于 stdio 服务器,Hermes 不会盲目传递你的完整 shell 环境。

只有显式配置的 env 加上安全基线才会被传递。这减少了意外泄露密钥的风险。

配置层面的暴露控制​

新的过滤支持同时也是一种安全控制:

  • 禁用你不希望模型看到的危险工具
  • 对敏感服务器只暴露最小白名单
  • 在不需要暴露该接口时,禁用资源/prompt 封装

示例用例​

GitHub 服务器,仅暴露最小 issue 管理接口​

mcp_servers:
  github:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    env:
      GITHUB_PERSONAL_ACCESS_TOKEN: "***"
    tools:
      include: [list_issues, create_issue, update_issue]
      prompts: false
      resources: false

使用方式:

Show me open issues labeled bug, then draft a new issue for the flaky MCP reconnection behavior.

Stripe 服务器,移除危险操作​

mcp_servers:
  stripe:
    url: "https://mcp.stripe.com"
    headers:
      Authorization: "Bearer ***"
    tools:
      exclude: [delete_customer, refund_payment]

使用方式:

Look up the last 10 failed payments and summarize common failure reasons.

文件系统服务器,限定单个项目根目录​

mcp_servers:
  project_fs:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/my-project"]

使用方式:

Inspect the project root and explain the directory layout.

故障排查​

MCP 服务器无法连接​

检查:

# 验证 MCP 依赖已安装(标准安装已包含)
cd ~/.hermes/hermes-agent && python -c "import pm; pm.sync_venv(['mcp'], explicit=True)"

node --version
npx --version

然后验证你的配置并重启 Hermes。

工具未出现​

可能原因:

  • 服务器连接失败
  • 发现过程失败
  • 你的过滤配置排除了这些工具
  • 该服务器不存在对应的实用工具能力
  • 服务器通过 enabled: false 被禁用

如果你是有意过滤,这是预期行为。

为什么资源或 prompt 实用工具没有出现?​

因为 Hermes 现在只在以下两个条件同时满足时才注册这些封装:

  • 你的配置允许它们
  • 服务器会话实际支持该能力

这是有意为之,保持工具列表的真实性。

并行工具调用​

默认情况下,MCP 工具按顺序执行——一次一个。如果你的 MCP 服务器暴露的工具可以安全并发运行(例如只读查询、独立 API 调用),可以选择启用并行执行:

mcp_servers:
  docs:
    command: "docs-server"
    supports_parallel_tool_calls: true

当 supports_parallel_tool_calls 为 true 时,Hermes 可能在单次工具调用批次中同时执行该服务器的多个工具,就像对内置只读工具(web_search、read_file 等)的处理方式一样。

警告

只对工具可以安全同时运行的 MCP 服务器启用并行调用。如果工具会读写共享状态、文件、数据库或外部资源,请在启用此设置前仔细评估读写竞争条件。

MCP Sampling 支持​

MCP 服务器可以通过 sampling/createMessage 协议向 Hermes 请求 LLM 推理。这允许 MCP 服务器代表自己请求 Hermes 生成文本——适用于需要 LLM 能力但没有自己模型访问权限的服务器。

Sampling 对所有 MCP 服务器默认启用(当 MCP SDK 支持时)。可在 sampling 键下按服务器配置:

mcp_servers:
  my_server:
    command: "my-mcp-server"
    sampling:
      enabled: true            # 启用 sampling(默认:true)
      model: "openai/gpt-4o"  # 覆盖 sampling 请求使用的模型(可选)
      max_tokens_cap: 4096     # 每次 sampling 响应的最大 token 数(默认:4096)
      timeout: 30              # 每次请求的超时时间,单位秒(默认:30)
      max_rpm: 10              # 速率限制:每分钟最大请求数(默认:10)
      max_tool_rounds: 5       # sampling 循环中的最大工具调用轮数(默认:5)
      allowed_models: []       # 服务器可请求的模型名称白名单(空 = 不限)
      log_level: "info"        # 审计日志级别:debug、info 或 warning(默认:info)

sampling 处理器包含滑动窗口速率限制器、按请求超时和工具循环深度限制,防止失控使用。每个服务器实例会跟踪指标(请求数、错误数、已用 token 数)。

如需对特定服务器禁用 sampling:

mcp_servers:
  untrusted_server:
    url: "https://mcp.example.com"
    sampling:
      enabled: false

将 Hermes 作为 MCP 服务器运行​

除了连接到 MCP 服务器,Hermes 也可以作为 MCP 服务器运行。这让其他支持 MCP 的 agent(Claude Code、Cursor、Codex 或任何 MCP 客户端)能够使用 Hermes 的消息能力——列出会话、读取消息历史,以及跨所有已连接平台发送消息。

适用场景​

  • 你希望 Claude Code、Cursor 或其他编程 agent 通过 Hermes 发送和读取 Telegram/Discord/Slack 消息
  • 你需要一个单一的 MCP 服务器,同时桥接 Hermes 所有已连接的消息平台
  • 你已经有一个运行中的 Hermes gateway,并已连接各平台

快速开始​

hermes mcp serve

这会启动一个 stdio MCP 服务器。进程生命周期由 MCP 客户端(而非你)管理。

MCP 客户端配置​

将 Hermes 添加到你的 MCP 客户端配置中。例如,在 Claude Code 的 ~/.claude/claude_desktop_config.json 中:

{
  "mcpServers": {
    "hermes": {
      "command": "hermes",
      "args": ["mcp", "serve"]
    }
  }
}

或者,如果你将 Hermes 安装在特定位置:

{
  "mcpServers": {
    "hermes": {
      "command": "/home/user/.hermes/hermes-agent/venv/bin/hermes",
      "args": ["mcp", "serve"]
    }
  }
}

可用工具​

MCP 服务器暴露 10 个工具,与 OpenClaw 的 channel bridge 接口一致,并额外提供一个 Hermes 专属的 channel 浏览器:

| | 工具 | 描述

| conversations_list | 列出活跃的消息会话。可按平台过滤或按名称搜索。

| conversation_get | 通过 session key 获取某个会话的详细信息。

| messages_read | 读取某个会话的近期消息历史。

| attachments_fetch | 从特定消息中提取非文本附件(图片、媒体)。

| events_poll | 从指定游标位置轮询新的会话事件。

| events_wait | 长轮询/阻塞,直到下一个事件到达(接近实时)。

| messages_send | 通过平台发送消息(例如 telegram:123456、discord:#general)。

| channels_list | 列出所有平台上可用的消息目标。

| permissions_list_open | 列出本次 bridge 会话中观察到的待审批请求。

| permissions_respond | 允许或拒绝待审批请求。

事件系统​

MCP 服务器包含一个实时事件桥,轮询 Hermes 的会话数据库以获取新消息。这让 MCP 客户端能够近实时感知新来的会话:

# 轮询新事件(非阻塞)
events_poll(after_cursor=0)

# 等待下一个事件(阻塞,直到超时)
events_wait(after_cursor=42, timeout_ms=30000)

事件类型:message、approval_requested、approval_resolved

事件队列存储在内存中,在 bridge 连接时开始工作。较旧的消息可通过 messages_read 获取。

选项​

hermes mcp serve              # 普通模式
hermes mcp serve --verbose    # 在 stderr 输出调试日志

工作原理​

MCP 服务器直接从 Hermes 的会话存储(~/.hermes/sessions/sessions.json 和 SQLite 数据库)读取会话数据。后台线程轮询数据库以获取新消息,并维护一个内存事件队列。发送消息时,使用与 Hermes agent 本身相同的 send_message 基础设施。

读取操作(列出会话、读取历史、轮询事件)不需要 gateway 运行。发送操作需要 gateway 运行,因为平台适配器需要活跃连接。

当前限制​

  • 内嵌的 hermes mcp serve 目前只暴露 stdio-only MCP 服务器。如果你需要 HTTP MCP 服务器,请运行单独的适配器——或者,更常见的做法是使用 Hermes 的 MCP 客户端侧,它已经同时支持 stdio 和 HTTP(mcp_servers.yaml / config.yaml 中的 url + headers;参见上方的 HTTP 服务器)。
  • 事件轮询间隔约 200ms,通过基于 mtime 优化的数据库轮询实现(文件未变化时跳过处理)
  • 暂不支持 claude/channel 推送通知协议
  • 仅支持纯文本发送(messages_send 不支持媒体/附件发送)

相关文档​

  • 在 Hermes 中使用 MCP
  • CLI 命令
  • 斜杠命令
  • 常见问题

网页搜索与提取

本页的 Python 依赖命令使用 PM 准备的源码环境。

依赖变更后,请重新激活该 checkout 并重启 Hermes。

Hermes Agent 内置两个可供模型调用的网页工具,由多个提供商支持:

  • web_search — 搜索网页并返回排序结果
  • web_extract — 从一个或多个 URL 获取并提取可读内容

两者均通过单一后端选择进行配置。提供商可通过 hermes tools 选择,或直接在 config.yaml 中设置。

后端​

| | 提供商 | 环境变量 | 搜索 | 提取 | 免费层级

| Firecrawl(默认) | FIRECRAWL_API_KEY | ✔ | ✔ | 500 积分/月

| SearXNG | SEARXNG_URL | ✔ | — | ✔ 免费(自托管)

| Brave Search(免费层级) | BRAVE_SEARCH_API_KEY | ✔ | — | 2 000 次查询/月

| DDGS (DuckDuckGo) | —(无需密钥) | ✔ | — | ✔ 免费

| Tavily | TAVILY_API_KEY | ✔ | ✔ | 1 000 次搜索/月

| Exa | EXA_API_KEY | ✔ | ✔ | 1 000 次搜索/月

| Parallel | PARALLEL_API_KEY | ✔ | ✔ | 付费

| xAI (Grok) | XAI_API_KEY 或 hermes auth login xai-oauth | ✔ | — | 付费(SuperGrok 或按 token 计费)

Brave Search、DDGS 和 xAI 均为仅搜索——如果同时需要 web_extract,可将其中任意一个与 Firecrawl/Tavily/Exa/Parallel 配合使用。DDGS 底层使用 ddgs Python 包;若尚未安装,请运行 python -c "import pm; pm.sync_venv(['ddgs'], explicit=True)"(或让 Hermes 在首次使用时懒加载安装)。xAI 通过 Responses API 运行 Grok 服务端的 web_search 工具——结果由 LLM 生成而非基于索引,因此标题、描述和 URL 选择均为模型输出(参见下方信任模型说明)。

按能力拆分: 搜索和提取可分别使用不同的提供商——例如搜索使用 SearXNG(免费),提取使用 Firecrawl。详见下方按能力配置。

Nous 订阅用户

如果您拥有付费 Nous Portal 订阅,网页搜索和提取可通过 Tool Gateway 使用托管网页搜索——无需 API 密钥。新安装可运行 hermes setup --portal 登录并一次性开启所有 gateway 工具;现有安装可通过 hermes tools 单独开启网页功能。

web_extract 如何处理长页面​

后端返回的原始页面 markdown 可能非常庞大(论坛帖子、文档站点、带嵌入评论的新闻文章)。为保持上下文窗口可用,web_extract 采用确定性字符预算 —— 不涉及任何 LLM 摘要:

| | 页面大小(字符数) | 处理方式

| 预算以内(默认 15 000) | 原样返回 —— 完整 markdown 直达 agent

| 超出预算 | 头+尾窗口(约 75% 头部 / 25% 尾部,按 markdown 行边界切分),并附带明确的 [TRUNCATED] 尾注。完整的干净文本存储到磁盘,尾注告知 agent 文件路径以及分页读取被省略中间部分的确切 read_file 调用

| 超过 2 000 000 | 存储的文本上限为 2 MB

每页预算可通过 config.yaml 中的 web.extract_char_limit 配置(默认 15000,范围限制在 2 000–500 000),agent 也可以通过工具的 char_limit 参数按调用提高。

当截断带来不便时​

如果您明确需要实时 DOM 而非提取的 markdown —— 例如提取内容很少的 JS 密集页面 —— 请改用 browser_navigate + browser_snapshot。浏览器工具返回实时无障碍树(超大页面受其自身快照上限约束)。

设置​

通过 hermes tools 快速设置​

运行 hermes tools,导航至 Web Search & Extract,选择一个提供商。向导会提示输入所需的 URL 或 API 密钥,并写入您的配置。

hermes tools

Firecrawl(默认)​

功能完整的搜索和提取。推荐大多数用户使用。

# ~/.hermes/.env
FIRECRAWL_API_KEY=fc-your-key-here

在 firecrawl.dev 获取密钥。免费层级包含每月 500 积分。

自托管 Firecrawl: 指向您自己的实例而非云端 API:

# ~/.hermes/.env
FIRECRAWL_API_URL=http://localhost:3002

设置 FIRECRAWL_API_URL 后,API 密钥为可选项(使用 USE_DB_AUTHENTICATION=false 禁用服务器认证)。

SearXNG(免费,自托管)​

SearXNG 是一个注重隐私的开源元搜索引擎,聚合来自 70 多个搜索引擎的结果。无需 API 密钥——只需将 Hermes 指向一个运行中的 SearXNG 实例。

SearXNG 为仅搜索——web_extract 需要单独的提取提供商。

方案 A — 使用 Docker 自托管(推荐)​

这为您提供无速率限制的私有实例。

1. 创建工作目录:

mkdir -p ~/searxng/searxng
cd ~/searxng

2. 编写 docker-compose.yml:

# ~/searxng/docker-compose.yml
services:
  searxng:
    image: searxng/searxng:latest
    container_name: searxng
    ports:
      - "8888:8080"
    volumes:
      - ./searxng:/etc/searxng:rw
    environment:
      - SEARXNG_BASE_URL=http://localhost:8888/
    restart: unless-stopped

3. 启动容器:

docker compose up -d

4. 启用 JSON API 格式:

SearXNG 默认禁用 JSON 输出。复制生成的配置并启用它:

# 从容器中复制自动生成的配置
docker cp searxng:/etc/searxng/settings.yml ~/searxng/searxng/settings.yml

打开 ~/searxng/searxng/settings.yml,找到 formats 块(约第 84 行):

# 修改前(默认——JSON 已禁用):
formats:
  - html

# 修改后(为 Hermes 启用 JSON):
formats:
  - html
  - json

5. 重启以应用更改:

docker cp ~/searxng/searxng/settings.yml searxng:/etc/searxng/settings.yml
docker restart searxng

6. 验证是否正常工作:

curl -s "http://localhost:8888/search?q=test&format=json" | python3 -c \
  "import sys,json; d=json.load(sys.stdin); print(f'{len(d[\"results\"])} results')"

您应该看到类似 10 results 的输出。如果收到 403 Forbidden,说明 JSON 格式仍未启用——请重新检查第 4 步。

7. 配置 Hermes:

# ~/.hermes/.env
SEARXNG_URL=http://localhost:8888

然后在 ~/.hermes/config.yaml 中选择 SearXNG 作为搜索后端:

web:
  search_backend: "searxng"

或通过 hermes tools → Web Search & Extract → SearXNG 设置。

方案 B — 使用公共实例​

公共 SearXNG 实例列表见 searx.space。筛选已启用 JSON 格式的实例(表格中有显示)。

# ~/.hermes/.env
SEARXNG_URL=https://searx.example.com

公共实例

公共实例有速率限制、可用性不稳定,且可能随时禁用 JSON 格式。生产环境强烈建议自托管。

将 SearXNG 与提取提供商配合使用​

SearXNG 负责搜索;web_extract 需要单独的提供商。使用按能力配置的键:

# ~/.hermes/config.yaml
web:
  search_backend: "searxng"
  extract_backend: "firecrawl"   # 或 tavily、exa、parallel

使用此配置,Hermes 对所有搜索查询使用 SearXNG,对 URL 提取使用 Firecrawl——将免费搜索与高质量提取相结合。

Tavily​

针对 AI 优化的搜索和提取,免费层级慷慨。

# ~/.hermes/.env
TAVILY_API_KEY=tvly-your-key-here

在 app.tavily.com 获取密钥。免费层级包含每月 1 000 次搜索。

Exa​

具有语义理解的神经搜索。适合研究和查找概念相关内容。

# ~/.hermes/.env
EXA_API_KEY=your-exa-key-here

在 exa.ai 获取密钥。免费层级包含每月 1 000 次搜索。

Parallel​

具备深度研究能力的 AI 原生搜索和提取。

# ~/.hermes/.env
PARALLEL_API_KEY=your-parallel-key-here

在 parallel.ai 申请访问权限。

xAI (Grok)​

通过 Responses API 将 web_search 路由至 Grok 服务端的 web_search 工具。Grok 执行实际搜索并以结构化 JSON 返回最佳结果。

支持两种凭证路径——无需新的环境变量,无需新的设置向导:

# ~/.hermes/.env(环境变量路径)
XAI_API_KEY=sk-xai-your-key-here

或对于 SuperGrok 订阅用户:

hermes auth login xai-oauth

然后选择 xAI 作为搜索后端:

# ~/.hermes/config.yaml
web:
  backend: "xai"

可选配置项:

web:
  backend: "xai"
  xai:
    model: grok-build-0.1        # web_search 所需的推理模型(默认)
    allowed_domains:             # 可选,最多 5 个——与 excluded_domains 互斥
      - arxiv.org
    excluded_domains:            # 可选,最多 5 个
      - example-spam.com
    timeout: 90                  # 秒(默认)

仅搜索——如果同时需要 web_extract,请与 Firecrawl / Tavily / Exa / Parallel 配合使用。遇到 401 时,提供商会执行一次强制 OAuth token 刷新并重试(覆盖窗口中途吊销和主动过期检查无法解码的不透明 token);环境变量凭证跳过重试。

信任模型

与基于索引的提供商(Brave、Tavily、Exa)返回逐字搜索引擎结果不同,xAI 是由 LLM 选择要呈现的 URL 并自行撰写标题和描述。查询的内容会影响输出,因此恶意构造的查询(例如通过 agent 获取的不可信上游输入注入)原则上可以引导 Grok 输出攻击者指定的 URL。对返回的 URL 应与对待任何模型生成链接一样——在获取前进行验证,尤其是当查询来自不可信输入时。

配置​

单一后端​

为所有网页功能设置一个提供商:

# ~/.hermes/config.yaml
web:
  backend: "searxng"   # firecrawl | searxng | brave-free | ddgs | tavily | exa | parallel | xai

按能力配置​

搜索和提取使用不同的提供商。这允许您将免费搜索(SearXNG)与付费提取提供商组合使用,反之亦然:

# ~/.hermes/config.yaml
web:
  search_backend: "searxng"     # 由 web_search 使用
  extract_backend: "firecrawl"  # 由 web_extract 使用

当按能力键为空时,两者均回退到 web.backend。当 web.backend 也为空时,后端根据存在的 API 密钥/URL 自动检测。

优先级顺序(按能力):

  • web.search_backend / web.extract_backend(显式按能力配置)
  • web.backend(共享回退)
  • 从环境变量自动检测

自动检测​

如果未显式配置后端,Hermes 根据已设置的凭证选择第一个可用的后端:

| | 存在的凭证 | 自动选择的后端

| FIRECRAWL_API_KEY 或 FIRECRAWL_API_URL | firecrawl

| PARALLEL_API_KEY | parallel

| TAVILY_API_KEY | tavily

| EXA_API_KEY | exa

| SEARXNG_URL | searxng

xAI Web Search 不在自动检测链中——设置了 XAI_API_KEY(或通过 xAI Grok OAuth 登录)不会自动将网页流量路由至 xAI,因为这些凭证同时用于推理/TTS/图像生成,用户可能希望为网页使用不同的后端。请通过 web.backend: "xai" 显式启用。

验证设置​

运行 hermes setup 查看检测到的网页后端:

✅ Web Search & Extract (searxng)

源码检出也可以在 PM 激活后检查模块。

使用你打算检查网页配置的 Hermes 数据目录:

# 从 Hermes 源码目录的干净 shell 运行
source ./activate
python -m tools.web_tools

这将打印活动后端及其状态:

✅ Web backend: searxng
   Using SearXNG (search only): http://localhost:8888

故障排查​

web_search 返回 {"success": false}​

  • 检查 SEARXNG_URL 是否可达:curl -s "http://localhost:8888/search?q=test&format=json"
  • 如果收到 HTTP 403,说明 JSON 格式已禁用——在 settings.yml 的 formats 列表中添加 json 并重启
  • 如果收到连接错误,容器可能未运行:docker ps | grep searxng

web_extract 提示"search-only backend"​

SearXNG 无法提取 URL 内容。将 web.extract_backend 设置为支持提取的提供商:

web:
  search_backend: "searxng"
  extract_backend: "firecrawl"  # 或 tavily / exa / parallel

SearXNG 返回 0 条结果​

部分公共实例禁用了某些搜索引擎或分类。请尝试:

  • 换一个查询词
  • 从 searx.space 换一个公共实例
  • 自托管实例以获得稳定结果

公共实例遭遇速率限制​

切换到自托管实例(参见上方方案 A)。使用 Docker,您自己的实例没有速率限制。

web_extract 返回截断内容并附有 [TRUNCATED] 尾注​

对于超出字符预算的页面这是预期行为。尾注会给出保存完整干净文本的磁盘文件,以及分页读取被省略中间部分的确切 read_file 调用。若要内联查看更多内容,请在 config.yaml 中调大 web.extract_char_limit,或在调用时传入更大的 char_limit。

可选技能:searxng-search​

对于需要直接通过 curl 使用 SearXNG 的 agent(例如作为网页工具集不可用时的回退),请安装 searxng-search 可选技能:

hermes skills install official/research/searxng-search

这将添加一个技能,教 agent 如何:

  • 通过 curl 或 Python 调用 SearXNG JSON API
  • 按分类筛选(general、news、science 等)
  • 处理分页和错误情况
  • 在 SearXNG 不可达时优雅降级

代码执行(程序化工具调用)

execute_code 工具允许 agent 编写调用 Hermes 工具的 Python 脚本,将多步骤工作流压缩至单次 LLM 对话轮次。脚本在 agent 宿主机的子进程中运行,通过 Unix 域套接字 RPC 与 Hermes 通信。

工作原理​

  • Agent 编写使用 from hermes_tools import ... 的 Python 脚本
  • Hermes 生成带有 RPC 函数的 hermes_tools.py 存根模块
  • Hermes 打开 Unix 域套接字并启动 RPC 监听线程
  • 脚本在子进程中运行——工具调用通过套接字传回 Hermes
  • 只有脚本的 print() 输出会返回给 LLM;中间工具结果不会进入上下文窗口
# The agent can write scripts like:
from hermes_tools import web_search, web_extract

results = web_search("Python 3.13 features", limit=5)
for r in results["data"]["web"]:
    content = web_extract([r["url"]])
    # ... filter and process ...
print(summary)

脚本内可用工具: web_search、web_extract、read_file、write_file、search_files、patch、terminal(仅前台模式)。

Agent 何时使用此功能​

当存在以下情况时,agent 会使用 execute_code:

  • 3 次及以上工具调用,且调用之间包含处理逻辑
  • 批量数据过滤或条件分支
  • 对结果进行循环处理

核心优势:中间工具结果不会进入上下文窗口——只有最终的 print() 输出会返回,大幅降低 token 用量。

实际示例​

数据处理流水线​

from hermes_tools import search_files, read_file
import json

# Find all config files and extract database settings
matches = search_files("database", path=".", file_glob="*.yaml", limit=20)
configs = []
for match in matches.get("matches", []):
    content = read_file(match["path"])
    configs.append({"file": match["path"], "preview": content["content"][:200]})

print(json.dumps(configs, indent=2))

多步骤网络调研​

from hermes_tools import web_search, web_extract
import json

# Search, extract, and summarize in one turn
results = web_search("Rust async runtime comparison 2025", limit=5)
summaries = []
for r in results["data"]["web"]:
    page = web_extract([r["url"]])
    for p in page.get("results", []):
        if p.get("content"):
            summaries.append({
                "title": r["title"],
                "url": r["url"],
                "excerpt": p["content"][:500]
            })

print(json.dumps(summaries, indent=2))

批量文件重构​

from hermes_tools import search_files, read_file, patch

# Find all Python files using deprecated API and fix them
matches = search_files("old_api_call", path="src/", file_glob="*.py")
fixed = 0
for match in matches.get("matches", []):
    result = patch(
        path=match["path"],
        old_string="old_api_call(",
        new_string="new_api_call(",
        replace_all=True
    )
    if "error" not in str(result):
        fixed += 1

print(f"Fixed {fixed} files out of {len(matches.get('matches', []))} matches")

构建与测试流水线​

from hermes_tools import terminal, read_file
import json

# Run tests, parse results, and report
result = terminal("cd /project && python -m pytest --tb=short -q 2>&1", timeout=120)
output = result.get("output", "")

# Parse test output
passed = output.count(" passed")
failed = output.count(" failed")
errors = output.count(" error")

report = {
    "passed": passed,
    "failed": failed,
    "errors": errors,
    "exit_code": result.get("exit_code", -1),
    "summary": output[-500:] if len(output) > 500 else output
}

print(json.dumps(report, indent=2))

执行模式​

execute_code 有两种执行模式,通过 ~/.hermes/config.yaml 中的 code_execution.mode 控制:

| | 模式 | 工作目录 | Python 解释器

| project(默认) | 会话的工作目录(与 terminal() 相同) | 活跃的 VIRTUAL_ENV / CONDA_PREFIX python,回退至 Hermes 自身的 python

| strict | 与用户项目隔离的临时暂存目录 | sys.executable(Hermes 自身的 python)

何时保持 project 模式: 当你希望 import pandas、from my_project import foo 或 open(".env") 等相对路径与 terminal() 中的行为一致时。这几乎是你始终想要的模式。

何时切换至 strict 模式: 当你需要最大可复现性时——希望无论用户激活哪个 venv,每次会话都使用相同的解释器,并且希望脚本与项目目录隔离(避免通过相对路径意外读取项目文件)。

# ~/.hermes/config.yaml
code_execution:
  mode: project   # or "strict"

project 模式的回退行为:若 VIRTUAL_ENV / CONDA_PREFIX 未设置、已损坏或指向低于 3.8 的 Python,解析器会干净地回退至 sys.executable——agent 始终有可用的解释器。

两种模式的安全关键不变量完全相同:

  • 环境变量清理(API key、token、凭据默认被剥离)
  • 工具白名单(脚本不能递归调用 execute_code、delegate_task 或 MCP 工具)
  • 资源限制(超时、stdout 上限、工具调用上限)

切换模式只改变脚本的运行位置和使用的解释器,不改变脚本可见的凭据或可调用的工具。

资源限制​

| | 资源 | 限制 | 说明

| 超时 | 5 分钟(300 秒) | 脚本先收到 SIGTERM,5 秒宽限期后收到 SIGKILL

| Stdout | 50 KB | 输出截断并附加 [output truncated at 50KB] 提示

| Stderr | 10 KB | 非零退出时包含在输出中,用于调试

| 工具调用 | 每次执行 50 次 | 达到上限时返回错误

所有限制均可通过 config.yaml 配置:

# In ~/.hermes/config.yaml
code_execution:
  mode: project      # project (default) | strict
  timeout: 300       # Max seconds per script (default: 300)
  max_tool_calls: 50 # Max tool calls per execution (default: 50)

脚本内工具调用的工作方式​

当脚本调用 web_search("query") 等函数时:

  • 调用被序列化为 JSON,通过 Unix 域套接字发送至父进程
  • 父进程通过标准 handle_function_call 处理器进行分发
  • 结果通过套接字发回
  • 函数返回解析后的结果

这意味着脚本内的工具调用与普通工具调用行为完全一致——相同的速率限制、相同的错误处理、相同的能力。唯一的限制是 terminal() 仅支持前台模式(不支持 background 或 pty 参数)。

错误处理​

脚本失败时,agent 会收到结构化的错误信息:

  • 非零退出码:stderr 包含在输出中,agent 可看到完整的 traceback
  • 超时:脚本被终止,agent 看到 "Script timed out after 300s and was killed."
  • 中断:若用户在执行期间发送新消息,脚本被终止,agent 看到 [execution interrupted — user sent a new message]
  • 工具调用上限:达到 50 次调用上限后,后续工具调用返回错误消息

响应始终包含 status(success/error/timeout/interrupted)、output、tool_calls_made 和 duration_seconds。

安全性​

安全模型

子进程在最小化环境中运行。API key、token 和凭据默认被剥离。脚本只能通过 RPC 通道访问工具——除非显式允许,否则无法从环境变量中读取密钥。

名称中包含 KEY、TOKEN、SECRET、PASSWORD、CREDENTIAL、PASSWD 或 AUTH 的环境变量会被排除。只有安全的系统变量(PATH、HOME、LANG、SHELL、PYTHONPATH、VIRTUAL_ENV 等)会被传递。

Skill 环境变量透传​

当 skill 在其 frontmatter 中声明 required_environment_variables 时,这些变量会在 skill 加载后自动透传至 execute_code 和 terminal 子进程。这使 skill 可以使用其声明的 API key,而不会削弱任意代码的安全态势。

对于非 skill 场景,可在 config.yaml 中显式添加变量白名单:

terminal:
  env_passthrough:
    - MY_CUSTOM_KEY
    - ANOTHER_TOKEN

详情参见安全指南。

Hermes 始终将脚本和自动生成的 hermes_tools.py RPC 存根写入临时暂存目录,执行完成后清理。在 strict 模式下,脚本也在该目录中运行;在 project 模式下,脚本在会话的工作目录中运行(暂存目录保留在 PYTHONPATH 中以确保导入正常解析)。子进程在独立的进程组中运行,以便在超时或中断时干净地终止。

execute_code 与 terminal 对比​

| | 使用场景 | execute_code | terminal

| 调用之间含逻辑的多步骤工作流 | ✅ | ❌

| 简单 shell 命令 | ❌ | ✅

| 过滤/处理大量工具输出 | ✅ | ❌

| 运行构建或测试套件 | ❌ | ✅

| 对搜索结果进行循环处理 | ✅ | ❌

| 交互式/后台进程 | ❌ | ✅

| 需要环境变量中的 API key | ⚠️ 仅通过透传 | ✅(大多数可透传)

经验法则: 需要在调用之间含逻辑地程序化调用 Hermes 工具时,使用 execute_code。运行 shell 命令、构建和进程时,使用 terminal。

平台支持​

代码执行依赖 Unix 域套接字,仅在 Linux 和 macOS 上可用。在 Windows 上会自动禁用——agent 回退至常规的顺序工具调用。

浏览器自动化

Hermes Agent 内置完整的浏览器自动化工具集,支持多种后端选项:

  • Browserbase 云端模式 — 通过 Browserbase 使用托管云端浏览器及反机器人工具
  • Browser Use 云端模式 — 通过 Browser Use 作为备选云端浏览器提供商
  • Firecrawl 云端模式 — 通过 Firecrawl 使用内置抓取功能的云端浏览器
  • Camofox 本地模式 — 通过 Camofox 实现本地反检测浏览(基于 Firefox 的指纹伪装)
  • 本地 Chromium 系 CDP — 使用 /browser connect 将浏览器工具连接到本地运行的 Chrome、Brave、Chromium 或 Edge 实例
  • 本地浏览器模式 — 通过 agent-browser CLI 和本地 Chromium 安装运行

所有模式下,Agent 均可导航网站、与页面元素交互、填写表单并提取信息。

概述​

页面以无障碍树(accessibility tree,基于文本的快照)表示,非常适合 LLM Agent 使用。交互元素会获得引用 ID(如 @e1、@e2),Agent 通过这些 ID 执行点击和输入操作。

核心能力:

  • 多提供商云端执行 — Browserbase、Browser Use 或 Firecrawl — 无需本地浏览器
  • 本地 Chromium 系集成 — 通过 CDP 连接正在运行的 Chrome、Brave、Chromium 或 Edge 浏览器,实现实时操控
  • 内置隐身功能 — 随机指纹、CAPTCHA 解决、住宅代理(Browserbase)
  • 会话隔离 — 每个任务拥有独立的浏览器会话
  • 自动清理 — 非活跃会话在超时后自动关闭
  • 视觉分析 — 截图 + AI 分析,实现视觉理解

配置​

Nous 订阅用户

如果您拥有付费 Nous Portal 订阅,可通过 Tool Gateway 使用浏览器自动化功能,无需单独的 API 密钥。新安装可运行 hermes setup --portal 登录并一次性开启所有 gateway 工具;已有安装可通过 hermes model 或 hermes tools 选择 Nous Subscription 作为浏览器提供商。

Browserbase 云端模式​

要使用 Browserbase 托管的云端浏览器,请添加:

# Add to ~/.hermes/.env
BROWSERBASE_API_KEY=***
BROWSERBASE_PROJECT_ID=your-project-id-here

在 browserbase.com 获取您的凭据。

Browser Use 云端模式​

要使用 Browser Use 作为云端浏览器提供商,请添加:

# Add to ~/.hermes/.env
BROWSER_USE_API_KEY=***

在 browser-use.com 获取 API 密钥。Browser Use 通过 REST API 提供云端浏览器。若同时设置了 Browserbase 和 Browser Use 凭据,Browserbase 优先。

Firecrawl 云端模式​

要使用 Firecrawl 作为云端浏览器提供商,请添加:

# Add to ~/.hermes/.env
FIRECRAWL_API_KEY=fc-***

在 firecrawl.dev 获取 API 密钥,然后选择 Firecrawl 作为浏览器提供商:

hermes setup tools
# → Browser Automation → Firecrawl

可选配置:

# Self-hosted Firecrawl instance (default: https://api.firecrawl.dev)
FIRECRAWL_API_URL=http://localhost:3002

# Session TTL in seconds (default: 300)
FIRECRAWL_BROWSER_TTL=600

混合路由:公网 URL 使用云端,LAN/localhost 使用本地​

配置云端提供商后,Hermes 会为解析到私有/回环/LAN 地址的 URL(localhost、127.0.0.1、192.168.x.x、10.x.x.x、172.16-31.x.x、*.local、*.lan、*.internal、IPv6 回环 ::1、链路本地 169.254.x.x)自动启动一个本地 Chromium 辅助进程。公网 URL 在同一对话中继续使用云端提供商。

这解决了常见的"本地开发但使用 Browserbase"场景 — Agent 可以截取 http://localhost:3000 上的仪表盘,同时抓取 https://github.com,无需切换提供商或禁用 SSRF 防护。云端提供商永远不会看到私有 URL。

该功能默认开启。如需禁用(所有 URL 均走已配置的云端提供商,与之前行为一致):

# ~/.hermes/config.yaml
browser:
  cloud_provider: browserbase
  auto_local_for_private_urls: false

禁用自动路由后,私有 URL 将被拒绝并返回 "Blocked: URL targets a private or internal address",除非同时设置 browser.allow_private_urls: true(允许云端提供商尝试访问,但通常无法成功,因为 Browserbase 等无法访问您的 LAN)。

要求:本地辅助进程使用与纯本地模式相同的 agent-browser CLI,因此需要先安装(hermes setup tools → Browser Automation 会自动安装)。从公网 URL 导航后重定向到私有地址的情况仍会被阻止(无法通过公网路径的重定向访问 LAN)。

Camofox 本地模式​

Camofox 是一个自托管的 Node.js 服务器,封装了 Camoufox(一个带有 C++ 指纹伪装的 Firefox 分支)。它无需云端依赖即可提供本地反检测浏览。

# Clone the Camofox browser server first
git clone https://github.com/jo-inc/camofox-browser
cd camofox-browser

# Build and start with Docker using the default container settings
# (auto-detects arch: aarch64 on M1/M2, x86_64 on Intel)
make up

# Stop and remove the default container
make down

# Force a clean rebuild (for example, after upgrading VERSION/RELEASE)
make reset

# Just download binaries without building
make fetch

# Override arch or version explicitly
make up ARCH=x86_64
make up VERSION=135.0.1 RELEASE=beta.24

make up 会立即启动默认容器。如需自定义运行时设置(如更大的 Node 堆内存、VNC 或持久化 profile 目录),请先构建镜像再手动运行:

# Build the image without starting the default container
make build

# Start with persistence, VNC live view, and a larger Node heap
mkdir -p ~/.camofox-docker
docker run -d \
  --name camofox-browser \
  --restart unless-stopped \
  -p 9377:9377 \
  -p 6080:6080 \
  -p 5901:5900 \
  -e CAMOFOX_PORT=9377 \
  -e ENABLE_VNC=1 \
  -e VNC_BIND=0.0.0.0 \
  -e VNC_RESOLUTION=1920x1080 \
  -e MAX_OLD_SPACE_SIZE=2048 \
  -v ~/.camofox-docker:/root/.camofox \
  camofox-browser:135.0.1-aarch64

启用 VNC 后,浏览器以有头模式运行,可在浏览器中通过 http://localhost:6080(noVNC)实时查看。也可使用原生 VNC 客户端连接 localhost:5901。

如果已运行过 make up,请在启动自定义容器前先停止并删除默认容器:

make down
# then run the custom docker run command above

然后在 ~/.hermes/.env 中设置:

CAMOFOX_URL=http://localhost:9377

或通过 hermes tools → Browser Automation → Camofox 进行配置。

设置 CAMOFOX_URL 仅提供服务器地址。要启用 Camofox,请在 hermes tools → Browser Automation 中选择 Camofox(写入 browser.cloud_provider: camofox)——一旦存在浏览器后端选择,仅设置 CAMOFOX_URL 不再自动切换后端(从未配置过的环境仍会自动检测)。

持久化浏览器会话​

默认情况下,每个 Camofox 会话使用随机身份 — Cookie 和登录状态不会在 Agent 重启后保留。要启用持久化浏览器会话,请在 ~/.hermes/config.yaml 中添加:

browser:
  camofox:
    managed_persistence: true

然后完全重启 Hermes 以使新配置生效。

嵌套路径很重要

Hermes 读取的是 browser.camofox.managed_persistence,而非顶层的 managed_persistence。常见错误写法:

# ❌ Wrong — Hermes ignores this
managed_persistence: true

如果该标志放在错误的路径下,Hermes 会静默回退到随机临时 userId,您的登录状态将在每次会话后丢失。

Hermes 的行为​

  • 向 Camofox 发送确定性的 profile 范围 userId,使服务器能够跨会话复用同一 Firefox profile。
  • 在清理时跳过服务端 context 销毁,使 Cookie 和登录状态在 Agent 任务间保留。
  • 将 userId 限定在当前 Hermes profile 范围内,不同 Hermes profile 对应不同浏览器 profile(profile 隔离)。

Hermes 不做的事​

  • 不会强制 Camofox 服务器持久化。Hermes 只发送稳定的 userId;服务器必须通过将该 userId 映射到持久化 Firefox profile 目录来支持它。
  • 如果您的 Camofox 服务器构建将每个请求视为临时的(例如始终调用 browser.newContext() 而不加载已存储的 profile),Hermes 无法使这些会话持久化。请确保运行的 Camofox 版本实现了基于 userId 的 profile 持久化。

验证是否正常工作​

  • 启动 Hermes 和 Camofox 服务器。
  • 在浏览器任务中打开 Google(或任意登录网站)并手动登录。
  • 正常结束浏览器任务。
  • 开始新的浏览器任务。
  • 再次打开同一网站 — 应仍处于登录状态。

如果第 5 步退出了登录,说明 Camofox 服务器未遵守稳定的 userId。请检查配置路径,确认编辑 config.yaml 后已完全重启 Hermes,并验证您的 Camofox 服务器版本是否支持基于用户的持久化 profile。

状态存储位置​

Hermes 从 profile 范围目录 ~/.hermes/browser_auth/camofox/(非默认 profile 则在 $HERMES_HOME 下的对应位置)派生稳定的 userId。实际浏览器 profile 数据存储在 Camofox 服务器端,以该 userId 为键。要完全重置持久化 profile,请在 Camofox 服务器端清除对应数据,并删除相应 Hermes profile 的状态目录。

外部管理的 Camofox 会话​

当另一个应用驱动可见的 Camofox 浏览器(桌面助手、自定义集成、另一个 Agent)时,可配置 Hermes 在同一身份下运行,而非启动独立的隔离 profile。

三个参数控制行为:

| | 设置 | 环境变量 | 效果

| browser.camofox.user_id | CAMOFOX_USER_ID | Hermes 创建标签页时使用的 Camofox userId。设置此项即进入"外部管理"模式。

| browser.camofox.session_key | CAMOFOX_SESSION_KEY | 创建标签页时发送的 sessionKey(即 listItemId)。用于接管时匹配已有标签页。未设置时默认为每任务值。

| browser.camofox.adopt_existing_tab | CAMOFOX_ADOPT_EXISTING_TAB | 为 true 时,Hermes 在首次使用时调用 GET /tabs?userId=<user_id> 并优先复用已有标签页,而非新建。

环境变量优先于 config.yaml。两种形式均可:

browser:
  camofox:
    user_id: shared-camofox
    session_key: visible-tab
    adopt_existing_tab: true
CAMOFOX_USER_ID=shared-camofox
CAMOFOX_SESSION_KEY=visible-tab
CAMOFOX_ADOPT_EXISTING_TAB=true

设置 user_id 后的变化:

  • Hermes 在任务结束时跳过破坏性清理(与 managed_persistence: true 相同)。其他应用的标签页/Cookie/profile 得以保留。
  • Hermes 不会调用 DELETE /sessions/<user_id> — 该端点会清除所有用户数据,若触发将销毁外部应用的会话。

标签页接管的工作方式(当 adopt_existing_tab: true 时):

  • 进程启动后首次调用浏览器工具时,Hermes 发出 GET /tabs?userId=<user_id>(5 秒超时)。
  • 若响应中有标签页的 listItemId == session_key,Hermes 接管该组中最近创建的一个。
  • 否则,Hermes 接管该用户最近创建的标签页(任意 listItemId)。
  • 若无标签页或请求失败,Hermes 在下次操作时回退到新建标签页。

接管仅在会话的 tab_id 填充之前触发一次。若外部应用在运行中关闭了被接管的标签页,下次浏览器工具调用将返回 Camofox 错误 — Hermes 不会在每次调用时重新轮询新标签页。

选择 session_key: 若要 Hermes 可靠地附加到特定已有标签页,请将 session_key 设置为外部应用创建该标签页时使用的 listItemId。若只设置 user_id 而不设置 session_key,Hermes 会生成每任务的 session_key(task_<id>)— Hermes 将与外部应用共享 Cookie 和 profile,但会并排打开自己的标签页而非复用已有标签页。

并发说明: 外部应用和 Hermes 可同时驱动同一 Camofox userId,但 Camofox 不会在客户端之间协调每个标签页的焦点。请在应用层协调所有权(例如,Hermes 运行时外部应用暂停)。

VNC 实时查看​

当 Camofox 以有头模式运行(带可见浏览器窗口)时,其健康检查响应中会暴露 VNC 端口。Hermes 自动发现此信息,并在导航响应中包含 VNC URL,Agent 可分享链接供您实时查看浏览器。

通过 CDP 连接本地 Chromium 系浏览器(/browser connect)​

除云端提供商外,您还可以通过 Chrome DevTools Protocol(CDP)将 Hermes 浏览器工具连接到本地运行的 Chrome、Brave、Chromium 或 Edge 实例。当您希望实时查看 Agent 操作、与需要自身 Cookie/会话的页面交互,或避免云端浏览器费用时,此方式非常有用。

备注

/browser connect 是交互式 CLI 斜杠命令 — 不由 gateway 分发。若在 WebUI、Telegram、Discord 或其他 gateway 聊天中尝试运行,消息将作为纯文本发送给 Agent,命令不会执行。请从终端启动 Hermes(hermes 或 hermes chat)并在那里执行 /browser connect。

在 CLI 中使用:

/browser connect                 # Auto-launch/connect to a local Chromium-family browser at http://127.0.0.1:9222
/browser connect ws://host:port  # Connect to a specific CDP endpoint
/browser status                  # Check current connection
/browser disconnect              # Detach and return to cloud/local mode

若浏览器尚未以远程调试模式运行,Hermes 将尝试自动启动支持的 Chromium 系浏览器并使用 --remote-debugging-port=9222。检测范围包括 Brave、Brave Origin/Nightly、Google Chrome、Chromium 和 Microsoft Edge,以及常见 Linux 安装路径和二进制名称(如 brave-origin、brave-origin-nightly、/opt/brave.com/brave-origin/brave-origin、/opt/brave.com/brave-origin-nightly/brave-origin、/opt/brave-bin/brave 和 /snap/bin/brave)。

提示

要手动启动带 CDP 的 Chromium 系浏览器,请使用专用的 user-data-dir,确保即使浏览器已以普通 profile 运行,调试端口也能正常开启:

# Linux — Brave
brave-browser \
  --remote-debugging-port=9222 \
  --user-data-dir=$HOME/.hermes/chrome-debug \
  --no-first-run \
  --no-default-browser-check &

# Linux — Google Chrome
google-chrome \
  --remote-debugging-port=9222 \
  --user-data-dir=$HOME/.hermes/chrome-debug \
  --no-first-run \
  --no-default-browser-check &

# macOS — Brave
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" \
  --remote-debugging-port=9222 \
  --user-data-dir="$HOME/.hermes/chrome-debug" \
  --no-first-run \
  --no-default-browser-check &

# macOS — Google Chrome
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --remote-debugging-port=9222 \
  --user-data-dir="$HOME/.hermes/chrome-debug" \
  --no-first-run \
  --no-default-browser-check &

然后启动 Hermes CLI 并运行 /browser connect。

为什么需要 --user-data-dir? 若不指定,在普通实例已运行时启动 Chromium 系浏览器通常只会在现有进程上打开新窗口 — 而该进程启动时未带 --remote-debugging-port,因此端口 9222 永远不会开启。专用的 user-data-dir 会强制启动新的浏览器进程,使调试端口正常监听。--no-first-run --no-default-browser-check 跳过新 profile 的首次启动向导。

通过 CDP 连接后,所有浏览器工具(browser_navigate、browser_click 等)将在您的实时浏览器实例上运行,而非启动云端会话。

WSL2 + Windows Chrome:优先使用 MCP 而非 /browser connect​

若 Hermes 在 WSL2 内运行,但您想控制的 Chrome 窗口在 Windows 宿主机上,/browser connect 通常不是最佳方案。

原因:

  • /browser connect 要求 Hermes 本身能访问可用的 CDP 端点
  • 现代 Chrome 实时调试会话通常暴露仅宿主机本地可访问的端点,WSL 无法像访问经典 9222 端口那样直接访问
  • 即使 Windows Chrome 可调试,最简洁的集成方式通常是让 Windows 侧的浏览器 MCP 服务器连接 Chrome,再让 Hermes 与该 MCP 服务器通信

对于此场景,建议通过 Hermes MCP 支持使用 chrome-devtools-mcp。

具体配置请参阅 MCP 指南:

  • 在 Hermes 中使用 MCP

本地浏览器模式​

若未设置任何云端凭据且未使用 /browser connect,Hermes 仍可通过由 agent-browser 驱动的本地 Chromium 安装使用浏览器工具。

可选环境变量​

# Residential proxies for better CAPTCHA solving (default: "true")
BROWSERBASE_PROXIES=true

# Advanced stealth with custom Chromium — requires Scale Plan (default: "false")
BROWSERBASE_ADVANCED_STEALTH=false

# Session reconnection after disconnects — requires paid plan (default: "true")
BROWSERBASE_KEEP_ALIVE=true

# Custom session timeout in milliseconds (default: project default)
# Examples: 600000 (10min), 1800000 (30min)
BROWSERBASE_SESSION_TIMEOUT=600000

# Inactivity timeout before auto-cleanup in seconds (default: 120)
BROWSER_INACTIVITY_TIMEOUT=120

# Extra Chromium launch flags (comma- or newline-separated). Hermes auto-injects
# `--no-sandbox,--disable-dev-shm-usage` when it detects root or AppArmor-restricted
# unprivileged user namespaces (Ubuntu 23.10+, DGX Spark, many container images),
# so most users don't need to set this. Set it manually only if you need a flag
# Hermes doesn't add automatically; setting it disables the auto-injection.
AGENT_BROWSER_ARGS=--no-sandbox

安装 agent-browser CLI​

npm install -g agent-browser
# Or install locally in the repo:
npm install

信息

browser 工具集必须包含在配置的 toolsets 列表中,或通过 hermes config set toolsets '["hermes-cli", "browser"]' 启用。

可用工具​

browser_navigate​

导航到指定 URL。必须在其他任何浏览器工具之前调用。初始化 Browserbase 会话。

Navigate to https://github.com/NousResearch

提示

对于简单的信息检索,优先使用 web_search 或 web_extract — 它们更快且成本更低。仅在需要与页面交互(点击按钮、填写表单、处理动态内容)时使用浏览器工具。

browser_snapshot​

获取当前页面无障碍树的文本快照。返回带有引用 ID(如 @e1、@e2)的交互元素,供 browser_click 和 browser_type 使用。

  • full=false(默认):仅显示交互元素的紧凑视图
  • full=true:完整页面内容

超过 15,000 字符的快照将按行边界自动截断(与 web_extract 使用相同的单页预算 —— 无 LLM 摘要)。发生截断时,完整快照会保存到 ~/.hermes/cache/web/,工具输出中包含文件路径和可直接使用的 read_file 调用,代理无需重新截图即可翻阅完整的可访问性树(包括被截断部分的元素 ref)。

browser_click​

点击快照中由引用 ID 标识的元素。

Click @e5 to press the "Sign In" button

browser_type​

向输入框输入文本。先清空字段,再输入新文本。

Type "hermes agent" into the search field @e3

browser_scroll​

向上或向下滚动页面以显示更多内容。

Scroll down to see more results

browser_press​

按下键盘按键。适用于提交表单或导航。

Press Enter to submit the form

支持的按键:Enter、Tab、Escape、ArrowDown、ArrowUp 等。

browser_back​

在浏览器历史记录中返回上一页。

browser_get_images​

列出当前页面上所有图片及其 URL 和 alt 文本。适用于查找需要分析的图片。

browser_vision​

截图并使用视觉 AI 进行分析。当文本快照无法捕获重要视觉信息时使用 — 尤其适用于 CAPTCHA、复杂布局或视觉验证挑战。

截图会持久保存,文件路径与 AI 分析结果一并返回。在消息平台(Telegram、Discord、Slack、WhatsApp)上,您可以要求 Agent 分享截图 — 它将通过 MEDIA: 机制作为原生图片附件发送。

What does the chart on this page show?

截图存储在 ~/.hermes/cache/screenshots/,24 小时后自动清理。

browser_console​

获取当前页面的浏览器控制台输出(log/warn/error 消息)及未捕获的 JavaScript 异常。对于检测无障碍树中不可见的静默 JS 错误至关重要。

Check the browser console for any JavaScript errors

使用 clear=True 可在读取后清空控制台,使后续调用只显示新消息。

browser_console 在带有 expression 参数调用时也可执行 JavaScript — 与 DevTools 控制台形式相同,结果以解析后的形式返回(JSON 序列化的对象变为 dict;原始值保持原始类型)。

browser_console(expression="document.querySelector('h1').textContent")
browser_console(expression="JSON.stringify(performance.timing)")

当当前会话存在活跃的 CDP 监督器时(通常适用于任何对 CDP 兼容后端运行过 browser_navigate 的会话),执行通过监督器的持久 WebSocket 进行 — 无子进程启动开销。否则回退到标准 agent-browser CLI 路径。两种方式行为完全相同,仅延迟有差异。

默认情况下执行不受限制 — 代理可以使用 fetch、读取存储、查询表单值并执行任何 DOM 提取。针对私有/内部地址的请求在非本地后端上仍会被拦截(SSRF 防护与此设置无关)。如果你在已登录的浏览器配置文件中浏览不可信页面,希望对敏感 JS 原语(Cookie、存储、剪贴板、网络调用、表单值)启用严格的黑名单,可在 config.yaml 中设置 browser.restrict_evaluate: true。注意该黑名单按原语名称匹配,因此也会拦截仅包含 fetch 或 cookie 等词的合法表达式。

browser_cdp​

原始 Chrome DevTools Protocol 直通 — 用于其他工具未覆盖的浏览器操作的逃生舱口。适用于原生对话框处理、iframe 范围内的执行、Cookie/网络控制,或 Agent 需要的任何 CDP 命令。

仅在会话启动时 CDP 端点可访问的情况下可用 — 即 /browser connect 已连接到运行中的 Chrome、Brave、Chromium 或 Edge 浏览器,或 config.yaml 中设置了 browser.cdp_url。默认本地 agent-browser 模式、Camofox 和云端提供商(Browserbase、Browser Use、Firecrawl)目前不向此工具暴露 CDP — 云端提供商有每会话 CDP URL,但实时会话路由是后续功能。

CDP 方法参考: https://chromedevtools.github.io/devtools-protocol/ — Agent 可通过 web_extract 访问特定方法页面以查阅参数和返回结构。

常见用法:

# List tabs (browser-level, no target_id)
browser_cdp(method="Target.getTargets")

# Handle a native JS dialog on a tab
browser_cdp(method="Page.handleJavaScriptDialog",
            params={"accept": true, "promptText": ""},
            target_id="<tabId>")

# Evaluate JS in a specific tab
browser_cdp(method="Runtime.evaluate",
            params={"expression": "document.title", "returnByValue": true},
            target_id="<tabId>")

# Get all cookies
browser_cdp(method="Network.getAllCookies")

浏览器级方法(Target.*、Browser.*、Storage.*)省略 target_id。页面级方法(Page.*、Runtime.*、DOM.*、Emulation.*)需要来自 Target.getTargets 的 target_id。每次无状态调用相互独立 — 调用间不保留会话状态。

跨域 iframe: 传入 frame_id(来自 browser_snapshot.frame_tree.children[] 中 is_oopif=true 的条目)可通过监督器的实时会话路由该 iframe 的 CDP 调用。这是在 Browserbase 上对跨域 iframe 执行 Runtime.evaluate 的方式,避免无状态 CDP 连接遭遇签名 URL 过期问题。示例:

browser_cdp(
  method="Runtime.evaluate",
  params={"expression": "document.title", "returnByValue": True},
  frame_id="<frame_id from browser_snapshot>",
)

同域 iframe 无需 frame_id — 在顶层 Runtime.evaluate 中使用 document.querySelector('iframe').contentDocument 即可。

browser_dialog​

响应原生 JS 对话框(alert / confirm / prompt / beforeunload)。在此工具出现之前,对话框会静默阻塞页面的 JavaScript 线程,后续 browser_* 调用会挂起或抛出异常;现在 Agent 可在 browser_snapshot 输出中看到待处理对话框并显式响应。

工作流程:

  • 调用 browser_snapshot。若对话框正在阻塞页面,将显示为 pending_dialogs: [{"id": "d-1", "type": "alert", "message": "..."}]。
  • 调用 browser_dialog(action="accept") 或 browser_dialog(action="dismiss")。对于 prompt() 对话框,传入 prompt_text="..." 提供响应内容。
  • 重新快照 — pending_dialogs 为空;页面 JS 线程已恢复。

检测通过持久 CDP 监督器自动进行 — 每个任务一个 WebSocket,订阅 Page/Runtime/Target 事件。监督器还会在快照中填充 frame_tree 字段,使 Agent 可查看当前页面的 iframe 结构,包括跨域(OOPIF)iframe。

可用性矩阵:

| | 后端 | 通过 pending_dialogs 检测 | 响应(browser_dialog 工具)

| 通过 /browser connect 或 browser.cdp_url 连接的本地 Chrome | ✓ | ✓ 完整工作流

| Browserbase | ✓ | ✓ 完整工作流(通过注入的 XHR 桥接)

| Camofox / 默认本地 agent-browser | ✗ | ✗(无 CDP 端点)

在 Browserbase 上的工作原理。 Browserbase 的 CDP 代理会在约 10ms 内在服务端自动关闭真实的原生对话框,因此无法使用 Page.handleJavaScriptDialog。监督器通过 Page.addScriptToEvaluateOnNewDocument 注入一段小脚本,将 window.alert/confirm/prompt 替换为同步 XHR。我们通过 Fetch.enable 拦截这些 XHR — 页面 JS 线程在 XHR 上保持阻塞,直到我们用 Agent 的响应调用 Fetch.fulfillRequest。prompt() 的返回值原样传回页面 JS。

对话框策略在 config.yaml 的 browser.dialog_policy 下配置:

| | 策略 | 行为

| must_respond(默认) | 捕获,在快照中显示,等待显式 browser_dialog() 调用。在 browser.dialog_timeout_s(默认 300 秒)后安全自动关闭,防止有问题的 Agent 永久阻塞。

| auto_dismiss | 捕获,立即关闭。Agent 仍可在 browser_state 历史中看到对话框,但无需操作。

| auto_accept | 捕获,立即接受。适用于导航带有频繁 beforeunload 提示的页面。

browser_snapshot.frame_tree 中的帧树上限为 30 帧、OOPIF 深度 2,以控制广告密集页面的负载大小。达到限制时会显示 truncated: true 标志;需要完整帧树的 Agent 可使用 browser_cdp 配合 Page.getFrameTree。

实际示例​

填写网页表单​

User: Sign up for an account on example.com with my email john@example.com

Agent workflow:
1. browser_navigate("https://example.com/signup")
2. browser_snapshot()  → sees form fields with refs
3. browser_type(ref="@e3", text="john@example.com")
4. browser_type(ref="@e5", text="SecurePass123")
5. browser_click(ref="@e8")  → clicks "Create Account"
6. browser_snapshot()  → confirms success

研究动态内容​

User: What are the top trending repos on GitHub right now?

Agent workflow:
1. browser_navigate("https://github.com/trending")
2. browser_snapshot(full=true)  → reads trending repo list
3. Returns formatted results

会话录制​

自动将浏览器会话录制为 WebM 视频文件:

browser:
  record_sessions: true  # default: false

启用后,录制在首次 browser_navigate 时自动开始,会话关闭时保存到 ~/.hermes/browser_recordings/。本地模式和云端模式(Browserbase)均支持。超过 72 小时的录制文件自动清理。

隐身功能​

Browserbase 提供自动隐身能力:

| | 功能 | 默认状态 | 说明

| 基础隐身 | 始终开启 | 随机指纹、视口随机化、CAPTCHA 解决

| 住宅代理 | 开启 | 通过住宅 IP 路由以提高访问成功率

| 高级隐身 | 关闭 | 自定义 Chromium 构建,需要 Scale 计划

| Keep Alive | 开启 | 网络中断后的会话重连

备注

若付费功能在您的计划中不可用,Hermes 会自动降级 — 先禁用 keepAlive,再禁用代理 — 确保免费计划也能正常浏览。

会话管理​

  • 每个任务通过 Browserbase 获得独立的浏览器会话
  • 非活跃会话在超时后自动清理(默认:2 分钟)
  • 后台线程每 30 秒检查一次过期会话
  • 进程退出时执行紧急清理,防止孤立会话
  • 通过 Browserbase API 释放会话(REQUEST_RELEASE 状态)

限制​

  • 基于文本的交互 — 依赖无障碍树,而非像素坐标
  • 快照大小 — 大型页面在 15,000 字符处被截断(与 web_extract 一致;无 LLM 摘要);完整快照会保存到 ~/.hermes/cache/web/,输出中给出路径供 read_file 翻阅
  • 会话超时 — 云端会话根据提供商计划设置过期
  • 费用 — 云端会话消耗提供商额度;对话结束或非活跃后会话自动清理。使用 /browser connect 可免费本地浏览。
  • 不支持文件下载 — 无法从浏览器下载文件

视觉与图像粘贴

Hermes Agent 支持多模态视觉——你可以直接将剪贴板中的图像粘贴到 CLI,让 Agent 对其进行分析、描述或处理。图像以 base64 编码的内容块形式发送给模型,因此任何支持视觉的模型均可处理。

工作原理​

  • 将图像复制到剪贴板(截图、浏览器图片等)
  • 使用以下任一方式附加图像
  • 输入问题并按 Enter
  • 图像以 [📎 Image #1] 徽章形式显示在输入框上方
  • 提交时,图像作为视觉内容块发送给模型

发送前可附加多张图像,每张图像都有独立徽章。按 Ctrl+C 可清除所有已附加图像。

图像以带时间戳的 PNG 文件名保存至 ~/.hermes/images/。

粘贴方式​

附加图像的方式取决于你的终端环境。并非所有方式在所有环境下均可用——以下是完整说明:

/paste 命令​

最可靠的显式图像附加备用方案。

/paste

输入 /paste 并按 Enter。Hermes 会检查剪贴板中是否有图像并附加。当你的终端重写了 Cmd+V/Ctrl+V,或剪贴板中只有图像而没有 bracketed-paste(括号粘贴)文本载荷可供检查时,这是最安全的选项。

Ctrl+V / Cmd+V​

Hermes 现在将粘贴处理为分层流程:

  • 优先进行普通文本粘贴
  • 若终端未能正常传递文本,则回退到原生剪贴板 / OSC52 文本
  • 当剪贴板或粘贴内容解析为图像或图像路径时,附加图像

这意味着粘贴的 macOS 截图临时路径和 file://... 图像 URI 可以立即附加,而不是以原始文本形式留在编辑器中。

注意

如果剪贴板中只有图像(无文本),终端仍无法直接发送二进制图像字节。请使用 /paste 作为显式图像附加的备用方案。

/terminal-setup(适用于 VS Code / Cursor / Windsurf)​

如果你在 macOS 上的 VS Code 系列集成终端中运行 TUI,Hermes 可以安装推荐的 workbench.action.terminal.sendSequence 绑定,以获得更好的多行输入及撤销/重做一致性:

/terminal-setup

当 Cmd+Enter、Cmd+Z 或 Shift+Cmd+Z 被 IDE 拦截时,此命令尤为有用。仅在本地机器上运行——不要在 SSH 会话中使用。

平台兼容性​

| | 环境 | /paste | Cmd/Ctrl+V | /terminal-setup | 备注

| macOS Terminal / iTerm2 | ✅ | ✅ | n/a | 最佳体验——原生剪贴板 + 截图路径恢复

| Apple Terminal | ✅ | ✅ | n/a | 若 Cmd+←/→/⌫ 被重写,使用 Ctrl+A / Ctrl+E / Ctrl+U 备用方案

| Linux X11 桌面 | ✅ | ✅ | n/a | 需要 xclip(apt install xclip)

| Linux Wayland 桌面 | ✅ | ✅ | n/a | 需要 wl-paste(apt install wl-clipboard)

| WSL2(Windows Terminal) | ✅ | ✅ | n/a | 使用 powershell.exe——无需额外安装

| VS Code / Cursor / Windsurf(本地) | ✅ | ✅ | ✅ | 推荐,以获得更好的 Cmd+Enter / 撤销 / 重做一致性

| VS Code / Cursor / Windsurf(SSH) | ❌² | ❌² | ❌³ | 请在本地机器上运行 /terminal-setup

| SSH 终端(任意) | ❌² | ❌² | n/a | 无法访问远程剪贴板

² 参见下方 SSH 与远程会话

³ 该命令写入本地 IDE 快捷键绑定,不应从远程主机运行

各平台配置说明​

macOS​

无需任何配置。 Hermes 使用 osascript(macOS 内置)读取剪贴板。如需更快的性能,可选择安装 pngpaste:

brew install pngpaste

Linux(X11)​

安装 xclip:

# Ubuntu/Debian
sudo apt install xclip

# Fedora
sudo dnf install xclip

# Arch
sudo pacman -S xclip

Linux(Wayland)​

现代 Linux 桌面(Ubuntu 22.04+、Fedora 34+)通常默认使用 Wayland。安装 wl-clipboard:

# Ubuntu/Debian
sudo apt install wl-clipboard

# Fedora
sudo dnf install wl-clipboard

# Arch
sudo pacman -S wl-clipboard

如何检查是否在使用 Wayland

echo $XDG_SESSION_TYPE
# "wayland" = Wayland,"x11" = X11,"tty" = 无显示服务器

WSL2​

无需额外配置。 Hermes 通过 /proc/version 自动检测 WSL2,并使用 powershell.exe 通过 .NET 的 System.Windows.Forms.Clipboard 访问 Windows 剪贴板。这是 WSL2 Windows 互操作的内置功能——powershell.exe 默认可用。

剪贴板数据通过 stdout 以 base64 编码的 PNG 格式传输,无需路径转换或临时文件。

WSLg 说明

如果你使用的是 WSLg(带 GUI 支持的 WSL2),Hermes 会优先尝试 PowerShell 路径,然后回退到 wl-paste。WSLg 的剪贴板桥接仅支持 BMP 格式的图像——Hermes 会使用 Pillow(如已安装)或 ImageMagick 的 convert 命令自动将 BMP 转换为 PNG。

验证 WSL2 剪贴板访问​

# 1. 检查 WSL 检测
grep -i microsoft /proc/version

# 2. 检查 PowerShell 是否可访问
which powershell.exe

# 3. 复制一张图像,然后检查
powershell.exe -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Clipboard]::ContainsImage()"
# 应输出 "True"

SSH 与远程会话​

通过 SSH 进行剪贴板图像粘贴无法完全正常工作。 当你 SSH 到远程机器时,Hermes CLI 运行在远程主机上。剪贴板工具(xclip、wl-paste、powershell.exe、osascript)读取的是其所在机器的剪贴板——即远程服务器,而非你的本地机器。因此,本地剪贴板中的图像在远程端无法访问。

文本有时仍可通过终端粘贴或 OSC52 传输,但图像剪贴板访问和本地截图临时路径始终绑定于运行 Hermes 的机器。

SSH 的变通方案​

-

上传图像文件——在本地保存图像,通过 scp、VSCode 文件浏览器(拖放)或任何文件传输方式上传到远程服务器,然后通过路径引用。(计划在未来版本中提供 /attach <filepath> 命令。)

-

使用 URL——如果图像可在线访问,直接在消息中粘贴 URL。Agent 可使用 vision_analyze 直接查看任意图像 URL。

-

X11 转发——使用 ssh -X 连接以转发 X11。这允许远程机器上的 xclip 访问你本地的 X11 剪贴板。需要本地运行 X 服务器(macOS 上为 XQuartz,Linux X11 桌面内置)。大图像传输较慢。

-

使用消息平台——通过 Telegram、Discord、Slack 或 WhatsApp 向 Hermes 发送图像。这些平台原生支持图像上传,不受剪贴板/终端限制的影响。

为什么终端无法粘贴图像​

这是一个常见的困惑来源,以下是技术说明:

终端是基于文本的界面。当你按下 Ctrl+V(或 Cmd+V)时,终端模拟器会:

  • 从剪贴板读取文本内容
  • 将其包裹在 bracketed paste(括号粘贴)转义序列中
  • 通过终端的文本流将其发送给应用程序

如果剪贴板中只有图像(无文本),终端没有任何内容可发送。目前没有标准的终端转义序列用于传输二进制图像数据,终端会直接忽略。

这就是为什么 Hermes 使用独立的剪贴板检查——它不通过终端粘贴事件接收图像数据,而是直接通过子进程调用操作系统级工具(osascript、powershell.exe、xclip、wl-paste)独立读取剪贴板。

支持的模型​

图像粘贴适用于任何支持视觉的模型。图像以 base64 编码的 data URL 形式,按 OpenAI 视觉内容格式发送:

{
  "type": "image_url",
  "image_url": {
    "url": "data:image/png;base64,..."
  }
}

大多数现代模型支持此格式,包括 GPT-4 Vision、Claude(带视觉)、Gemini,以及通过 OpenRouter 提供服务的开源多模态模型。

图像路由(视觉模型 vs 纯文本模型)​

当用户附加图像时——无论来自 CLI 剪贴板、gateway(Telegram/Discord 图片)还是其他入口——Hermes 会根据当前模型是否支持视觉进行路由:

| | 你的模型 | 图像处理方式

| 支持视觉的模型(GPT-4V、Claude with vision、Gemini、Qwen-VL、MiMo-VL、DeepSeek Flash / V4.1-Flash 等) | 使用上述提供商原生图像内容格式,以真实像素发送。无文本摘要层。

| 纯文本模型(DeepSeek V4 Pro、DeepSeek V3、较小的开源模型、旧版纯对话端点) | 通过 vision_analyze 辅助工具路由——辅助视觉模型描述图像,文本描述注入对话。

无需手动配置——Hermes 在提供商元数据中查找当前模型的能力并自动选择正确路径。实际效果:你可以在会话中途切换视觉模型与非视觉模型,图像处理"开箱即用",无需更改工作流。纯文本模型会获得关于图像的连贯上下文,而不是一个会被拒绝的损坏多模态载荷。

处理文本描述路径的辅助模型可在 auxiliary.vision 下配置——参见辅助模型。

vision_analyze 具有相同的双重行为​

vision_analyze 工具本身遵循相同的路由逻辑。当当前主模型支持视觉,且其提供商支持在工具结果中包含图像内容(目前为 Anthropic、OpenAI、Azure-OpenAI 和 Gemini 3.x 技术栈),vision_analyze 会跳过辅助描述器,直接将原始图像像素作为多模态工具结果信封返回。主模型在下一轮会原生看到图像——无辅助调用、无文本摘要信息损失、无额外延迟。

对于纯文本主模型(或工具结果通道不支持图像的提供商),vision_analyze 回退到旧路径:请求已配置的辅助视觉模型描述图像,并以纯文本形式返回描述。无论哪种情况,调用工具的签名相同——工具在运行时根据当前模型决定采用哪条路径。

原生嵌入伴随整个会话:vision.embed_target_bytes 与 vision.max_calls_per_image​

原生 vision_analyze 的结果会把图片烘焙进工具结果,而该结果会在本会话之后的每一次 API 调用中重复发送。config.yaml 中的两个键用于限制这笔反复产生的开销:

vision:
  embed_target_bytes: 262144   # 单次嵌入的字节预算;限制在 64 KiB..4 MiB(默认 256 KB)
  max_calls_per_image: 3       # 未设置 = 委派子代理内为 3,主代理不限
  • embed_target_bytes — 超过预算(或宽于 1568 px)的图片会被缩小为能放进预算的 JPEG。256 KB 足以让普通截图保持廉价,但密集的手机表格截图在这个大小下可能无法辨认;当模型不断称数字"无法读取"时,请调高该值(例如 1048576)。原生传递的浏览器截图使用同一预算。
  • max_calls_per_image — 同一张图片(包括其区域裁剪;本地路径按解析后的路径比较)在一个会话中最多可被嵌入的次数。达到上限后,工具返回 "vision_analyze refused: this image has already been loaded into context N time(s) …" 而不是再次嵌入,让模型基于已看到的内容作答。未设置时只有 delegate_task 委派的子代理受限(上限 3):它们无人值守,无法在循环中途从 CLI 被引导,曾有一次重复加载循环在五个文件上烧掉了 158 次调用。设置一个数字可限制所有会话,0 则表示完全不限。

语音模式

Hermes Agent 支持在 CLI 和消息平台上进行完整的语音交互。通过麦克风与 Agent 对话,听取语音回复,并在 Discord 语音频道中进行实时语音对话。

如需包含推荐配置和实际使用模式的实践指南,请参阅 使用 Hermes 的语音模式。

前提条件​

使用语音功能前,请确保已完成以下准备:

  • 已安装 Hermes Agent — 通过安装脚本(参见 安装)
  • 已配置 LLM 提供商 — 运行 hermes model 或在 ~/.hermes/.env 中设置首选提供商的凭据
  • 基础设置正常 — 运行 hermes 验证 Agent 能够响应文字消息,再启用语音功能

提示

~/.hermes/ 目录和默认的 config.yaml 会在首次运行 hermes 时自动创建。只需手动创建 ~/.hermes/.env 来存放 API 密钥。

Nous Portal 同时覆盖两项

付费的 Nous Portal 订阅通过 Tool Gateway 同时提供 LLM(第 2 步)和 OpenAI TTS — 无需单独的 OpenAI 密钥。全新安装时,hermes setup --portal 可一次性完成两项配置。

概览​

| | 功能 | 平台 | 说明

| 交互式语音 | CLI | 按 Ctrl+B 开始录音,Agent 自动检测静音并回复

| 自动语音回复 | Telegram、Discord | Agent 在文字回复的同时发送语音音频

| 语音频道 | Discord | Bot 加入语音频道,监听用户发言并语音回复

环境要求​

Python 包​

通过 hermes tools 配置语音提供商。缺失的内置功能依赖由 PM 按策略和目标平台支持准备。

如果选择的环境改变,请按提示重启 Hermes。

桌面包预装其支持的引擎;Docker 使用较小集合并关闭按需安装。

不要修改签名载荷或系统 Python。手动开发环境参见开发配置。

| | Extra | 包 | 用途

| voice | sounddevice、numpy,以及支持平台上的 Faster-Whisper | CLI 音频和本地 STT

| audio-io | sounddevice、numpy | 不包含本地 STT 的麦克风和播放支持

| messaging | discord.py[voice]、python-telegram-bot、aiohttp | Discord 和 Telegram

| tts-premium | elevenlabs | ElevenLabs TTS

原生 Windows ARM64 和 Intel macOS 不包含 Faster-Whisper,请使用云端或命令式 STT。

all 不代表所有语音或唤醒引擎。NeuTTS 是单独的可选运行时,首次使用会下载模型。

信息

discord.py[voice] 会自动安装 PyNaCl(用于语音加密)和 opus 绑定。这是 Discord 语音频道支持的必要条件。

系统依赖​

# macOS
brew install portaudio ffmpeg opus
brew install espeak-ng   # for NeuTTS

# Ubuntu/Debian
sudo apt install portaudio19-dev ffmpeg libopus0
sudo apt install espeak-ng   # for NeuTTS

| | 依赖项 | 用途 | 适用场景

| PortAudio | 麦克风输入和音频播放 | CLI 语音模式

| ffmpeg | 音频格式转换(MP3 → Opus、PCM → WAV) | 所有平台

| Opus | Discord 语音编解码器 | Discord 语音频道

| espeak-ng | Phonemizer 后端 | 本地 NeuTTS 提供商

API 密钥​

添加到 ~/.hermes/.env:

# 语音转文字(STT)— 本地提供商完全不需要密钥
# 本地 Faster-Whisper 由 PM 在支持的目标上准备,无需 STT API key。
GROQ_API_KEY=your-key                 # Groq Whisper — 速度快,有免费额度(云端)
VOICE_TOOLS_OPENAI_KEY=your-key       # OpenAI Whisper — 付费(云端)

# 文字转语音(TTS,可选 — Edge TTS 和 NeuTTS 无需任何密钥)
ELEVENLABS_API_KEY=***           # ElevenLabs — 高级音质
# 上方的 VOICE_TOOLS_OPENAI_KEY 同时启用 OpenAI TTS

提示

如果已安装 faster-whisper,语音模式的 STT 无需任何 API 密钥即可运行。模型(base 约 150 MB)会在首次使用时自动下载。

CLI 语音模式​

语音模式在经典 CLI(hermes chat)和 TUI(hermes --tui)中均可使用。两者行为完全一致 — 相同的斜杠命令、相同的 VAD(语音活动检测)静音检测、相同的流式 TTS、相同的幻觉过滤器。TUI 额外将崩溃诊断日志转发至 ~/.hermes/logs/,以便在异常音频后端出现按键录音失败时提供完整堆栈跟踪,而非静默消失。

快速开始​

启动 CLI 并启用语音模式:

hermes                # 启动交互式 CLI

然后在 CLI 中使用以下命令:

/voice          切换语音模式开/关
/voice on       启用语音模式
/voice off      禁用语音模式
/voice tts      切换 TTS 输出
/voice status   显示当前状态

工作原理​

  • 使用 hermes 启动 CLI,并通过 /voice on 启用语音模式
  • 按下 Ctrl+B — 播放提示音(880Hz),开始录音
  • 开始说话 — 实时音频电平条显示输入状态:● [▁▂▃▅▇▇▅▂] ❯
  • 停止说话 — 静音 3 秒后自动停止录音
  • 两声提示音(660Hz)确认录音结束
  • 音频通过 Whisper 转录后发送给 Agent
  • 如果启用了 TTS,Agent 的回复将以语音朗读
  • 录音自动重新开始 — 无需按任何键即可继续说话

此循环持续进行,直到在录音过程中按下 Ctrl+B(退出连续模式),或连续 3 次录音均未检测到语音为止。

提示

录音键可通过 ~/.hermes/config.yaml 中的 voice.record_key 配置(默认:ctrl+b)。

静音检测​

两阶段算法检测您是否已停止说话:

  • 语音确认 — 等待音频 RMS 值超过阈值(200)至少 0.3 秒,允许音节间的短暂停顿
  • 结束检测 — 语音确认后,持续静音 3.0 秒即触发停止

如果 15 秒内完全未检测到语音,录音自动停止。

silence_threshold 和 silence_duration 均可在 config.yaml 中配置。也可通过 voice.beep_enabled: false 禁用录音开始/结束提示音。

流式 TTS​

启用 TTS 后,Agent 在生成文字的同时逐句朗读回复 — 无需等待完整响应:

  • 将文字增量缓冲为完整句子(最少 20 个字符)
  • 去除 Markdown 格式和 <think> 块
  • 实时逐句生成并播放音频

幻觉过滤器​

Whisper 有时会从静音或背景噪音中生成幻觉文字(如"Thank you for watching"、"Subscribe"等)。Agent 使用包含 26 个已知幻觉短语(覆盖多种语言)的列表以及能捕获重复变体的正则表达式模式对其进行过滤。

Gateway 语音回复(Telegram 和 Discord)​

如果尚未设置消息机器人,请参阅对应平台的指南:

  • Telegram 设置指南
  • Discord 设置指南

启动 gateway 以连接到消息平台:

hermes gateway        # 启动 gateway(连接到已配置的平台)
hermes gateway setup  # 首次配置的交互式设置向导

Discord:频道与私信​

Bot 在 Discord 上支持两种交互模式:

| | 模式 | 交互方式 | 是否需要 @提及 | 设置

| 私信(DM) | 打开 Bot 的个人资料 → "发消息" | 否 | 立即可用

| 服务器频道 | 在 Bot 所在的文字频道中发言 | 是(@botname) | Bot 必须被邀请到服务器

私信(个人使用推荐): 直接与 Bot 开启私信并发送消息 — 无需 @提及。语音回复和所有命令与在频道中使用完全相同。

服务器频道: Bot 仅在被 @提及时响应(例如 @hermesbyt4 你好)。请确保从提及弹窗中选择 Bot 用户,而非同名角色。

提示

如需在服务器频道中禁用提及要求,在 ~/.hermes/.env 中添加:

DISCORD_REQUIRE_MENTION=false

或将特定频道设置为自由响应模式(无需提及):

DISCORD_FREE_RESPONSE_CHANNELS=123456789,987654321

命令​

以下命令在 Telegram 和 Discord(私信和文字频道)中均可使用:

/voice          切换语音模式开/关
/voice on       仅在您发送语音消息时回复语音
/voice tts      对所有消息回复语音
/voice off      禁用语音回复
/voice status   显示当前设置

模式​

| | 模式 | 命令 | 行为

| off | /voice off | 仅文字(默认)

| voice_only | /voice on | 仅当您发送语音消息时才语音回复

| all | /voice tts | 对每条消息均语音回复

语音模式设置在 gateway 重启后保持不变。

平台投递​

| | 平台 | 格式 | 说明

| Telegram | 语音气泡(Opus/OGG) | 在聊天中内联播放。如需要,ffmpeg 将 MP3 转换为 Opus

| Discord | 原生语音气泡(Opus/OGG) | 像用户语音消息一样内联播放。如语音气泡 API 失败则回退为文件附件

Discord 语音频道​

最具沉浸感的语音功能:Bot 加入 Discord 语音频道,监听用户发言,转录语音,通过 Agent 处理后,在语音频道中语音回复。

设置​

1. Discord Bot 权限​

如果您已为文字功能设置了 Discord Bot(参见 Discord 设置指南),需要额外添加语音权限。

前往 Discord 开发者门户 → 您的应用 → Installation → Default Install Settings → Guild Install:

在现有文字权限基础上添加以下权限:

| | 权限 | 用途 | 是否必需

| Connect | 加入语音频道 | 是

| Speak | 在语音频道中播放 TTS 音频 | 是

| Use Voice Activity | 检测用户是否正在说话 | 推荐

更新后的权限整数:

| | 级别 | 整数 | 包含内容

| 仅文字 | 309237763136 | 查看频道、发送消息、读取历史、嵌入内容、附件、帖子、反应、创建公开帖子

| 文字 + 语音 | 309240908864 | 以上所有 + Connect、Speak

使用更新后的权限 URL 重新邀请 Bot:

https://discord.com/oauth2/authorize?client_id=YOUR_APP_ID&scope=bot+applications.commands&permissions=309240908864

将 YOUR_APP_ID 替换为开发者门户中的应用 ID。

注意

将 Bot 重新邀请到已加入的服务器只会更新其权限,不会将其移除。不会丢失任何数据或配置。

2. 特权 Gateway Intents​

在 开发者门户 → 您的应用 → Bot → Privileged Gateway Intents 中,启用以下三项:

| | Intent | 用途

| Presence Intent | 检测用户在线/离线状态

| Server Members Intent | 将 DISCORD_ALLOWED_USERS 中的用户名解析为数字 ID(条件性)

| Message Content Intent | 读取频道中的文字消息内容

Message Content Intent 为必需项。Server Members Intent 仅在 DISCORD_ALLOWED_USERS 列表使用用户名时才需要 — 如果使用数字用户 ID,可以关闭。语音频道中 SSRC → user_id 的映射来自 Discord 语音 WebSocket 上的 SPEAKING opcode,不需要 Server Members Intent。

3. Opus 编解码器​

运行 gateway 的机器上必须安装 Opus 编解码器库:

# macOS (Homebrew)
brew install opus

# Ubuntu/Debian
sudo apt install libopus0

Bot 会从以下路径自动加载编解码器:

  • macOS: /opt/homebrew/lib/libopus.dylib
  • Linux: libopus.so.0

4. 环境变量​

# ~/.hermes/.env

# Discord bot(已为文字功能配置)
DISCORD_BOT_TOKEN=your-bot-token
DISCORD_ALLOWED_USERS=your-user-id

# 本地 Faster-Whisper 由 PM 在支持的目标上准备,无需 STT API key。
# GROQ_API_KEY=your-key            # 替代方案:云端,速度快,有免费额度

# TTS — 可选。Edge TTS 和 NeuTTS 无需密钥。
# ELEVENLABS_API_KEY=***      # 高级音质
# VOICE_TOOLS_OPENAI_KEY=***  # OpenAI TTS / Whisper

启动 Gateway​

hermes gateway        # 使用现有配置启动

Bot 应在几秒内在 Discord 中上线。

命令​

在 Bot 所在的 Discord 文字频道中使用以下命令:

/voice join      Bot 加入您当前所在的语音频道
/voice channel   /voice join 的别名
/voice leave     Bot 断开语音频道连接
/voice status    显示语音模式和已连接的频道

信息

运行 /voice join 前,您必须已在某个语音频道中。Bot 会加入您所在的语音频道。

工作原理​

Bot 加入语音频道后:

  • 独立监听每位用户的音频流
  • 检测静音 — 至少 0.5 秒语音后出现 1.5 秒静音即触发处理
  • 转录音频(通过本地、Groq 或 OpenAI 的 Whisper STT)
  • 处理完整的 Agent 流水线(会话、工具、记忆)
  • 语音回复通过 TTS 在语音频道中播放

文字频道集成​

Bot 在语音频道中时:

  • 转录内容会出现在文字频道中:[Voice] @user: 您说的内容
  • Agent 回复同时以文字发送到频道并在语音频道中朗读
  • 文字频道为发出 /voice join 命令的那个频道
  • 在另一个文字频道再次运行 /voice join 会把绑定移到那里;移动前录到的语音(无论仍在转录还是尚未说完)都会被丢弃,不会发到新频道

回声消除​

Bot 在播放 TTS 回复时会自动暂停音频监听,防止听到并重复处理自身的输出。

访问控制​

只有 DISCORD_ALLOWED_USERS 或 DISCORD_ALLOWED_ROLES 允许的用户才能通过语音进行交互;每次说话时都会为说话者检查身份组。其他用户的音频会被静默忽略。

# ~/.hermes/.env
DISCORD_ALLOWED_USERS=284102345871466496

配置参考​

config.yaml​

# 语音录制(CLI)
voice:
  record_key: "ctrl+b"            # 开始/停止录音的按键
  max_recording_seconds: 120       # 最大录音时长
  auto_tts: false                  # 启用语音模式时自动开启 TTS
  beep_enabled: true               # 播放录音开始/结束提示音
  silence_threshold: 200           # 静音判定的 RMS 电平(0-32767)
  silence_duration: 3.0            # 自动停止前的静音秒数

# 语音转文字(STT)
stt:
  enabled: true                     # 设为 false 可跳过自动转录 —
                                    # gateway 仍会缓存音频文件并将其路径
                                    # 作为入站消息的一部分传递给 Agent,
                                    # 适用于自定义流水线
                                    # (说话人分离、对齐、归档等)
  provider: "local"                  # "local"(免费)| "groq" | "openai" | "mistral" | "xai"
  local:
    model: "base"                    # tiny, base, small, medium, large-v3, turbo
  # model: "whisper-1"              # 旧版:在未设置 provider 时使用

# 文字转语音(TTS)
tts:
  provider: "edge"                 # "edge"(免费)| "elevenlabs" | "openai" | "neutts" | "minimax" | "mistral" | "gemini" | "xai" | "kittentts" | "piper"
  edge:
    voice: "en-US-AriaNeural"      # 322 种声音,74 种语言
  elevenlabs:
    voice_id: "pNInz6obpgDQGcFmaJgB"    # Adam
    model_id: "eleven_multilingual_v2"
  openai:
    model: "gpt-4o-mini-tts"
    voice: "alloy"                 # alloy, echo, fable, onyx, nova, shimmer
    base_url: "https://api.openai.com/v1"  # 可选:覆盖为自托管或兼容 OpenAI 的端点
  neutts:
    ref_audio: ''
    ref_text: ''
    model: neuphonic/neutts-air-q4-gguf
    device: cpu

环境变量​

# 语音转文字提供商(本地无需密钥)
# 本地 Faster-Whisper 由 PM 在支持的目标上准备,无需 STT API key。
GROQ_API_KEY=...                    # Groq Whisper(速度快,有免费额度)
VOICE_TOOLS_OPENAI_KEY=...         # OpenAI Whisper(付费)

# STT 高级覆盖(可选)
STT_GROQ_MODEL=whisper-large-v3-turbo    # 覆盖默认 Groq STT 模型
STT_OPENAI_MODEL=whisper-1               # 覆盖默认 OpenAI STT 模型
GROQ_BASE_URL=https://api.groq.com/openai/v1     # 自定义 Groq 端点
STT_OPENAI_BASE_URL=https://api.openai.com/v1    # 自定义 OpenAI STT 端点

# 文字转语音提供商(Edge TTS 和 NeuTTS 无需密钥)
ELEVENLABS_API_KEY=***             # ElevenLabs(高级音质)
# 上方的 VOICE_TOOLS_OPENAI_KEY 同时启用 OpenAI TTS

# Discord 语音频道
DISCORD_BOT_TOKEN=...
DISCORD_ALLOWED_USERS=...

STT 提供商对比​

| | 提供商 | 模型 | 速度 | 质量 | 费用 | 需要 API 密钥

| 本地 | base | 快(取决于 CPU/GPU) | 良好 | 免费 | 否

| 本地 | small | 中等 | 较好 | 免费 | 否

| 本地 | large-v3 | 慢 | 最佳 | 免费 | 否

| Groq | whisper-large-v3-turbo | 非常快(约 0.5 秒) | 良好 | 免费额度 | 是

| Groq | whisper-large-v3 | 快(约 1 秒) | 较好 | 免费额度 | 是

| OpenAI | whisper-1 | 快(约 1 秒) | 良好 | 付费 | 是

| OpenAI | gpt-4o-transcribe | 中等(约 2 秒) | 最佳 | 付费 | 是

| OpenAI | gpt-transcribe | 快 | 最佳 | 付费($0.0045/分钟) | 是

提供商优先级(自动回退):本地 > groq > openai

TTS 提供商对比​

| | 提供商 | 质量 | 费用 | 延迟 | 需要密钥

| Edge TTS | 良好 | 免费 | 约 1 秒 | 否

| ElevenLabs | 优秀 | 付费 | 约 2 秒 | 是

| OpenAI TTS | 良好 | 付费 | 约 1.5 秒 | 是

| NeuTTS | 良好 | 免费 | 取决于 CPU/GPU | 否

NeuTTS 使用上方的 tts.neutts 配置块。

故障排查​

"No audio device found"(CLI)​

PortAudio 未安装:

brew install portaudio    # macOS
sudo apt install portaudio19-dev  # Ubuntu

Bot 在 Discord 服务器频道中不响应​

Bot 在服务器频道中默认需要 @提及。请确认:

  • 输入 @ 后选择 Bot 用户(带有 #discriminator),而非同名角色
  • 或改用私信 — 无需提及
  • 或在 ~/.hermes/.env 中设置 DISCORD_REQUIRE_MENTION=false

Bot 加入语音频道但听不到我说话​

  • 检查您的 Discord 用户 ID 是否在 DISCORD_ALLOWED_USERS 中
  • 确认您在 Discord 中未被静音
  • Bot 需要收到 Discord 的 SPEAKING 事件才能映射您的音频 — 加入后请在几秒内开始说话

Bot 能听到我说话但不响应​

  • 验证 STT 是否可用:安装 faster-whisper(无需密钥)或设置 GROQ_API_KEY / VOICE_TOOLS_OPENAI_KEY
  • 检查 LLM 模型是否已配置且可访问
  • 查看 gateway 日志:tail -f ~/.hermes/logs/gateway.log

Bot 有文字回复但语音频道中没有声音​

  • TTS 提供商可能出现故障 — 检查 API 密钥和配额
  • Edge TTS(免费,无需密钥)是默认回退选项
  • 检查日志中的 TTS 错误

Whisper 返回乱码文字​

幻觉过滤器会自动处理大多数情况。如果仍然出现幻觉转录:

  • 在更安静的环境中使用
  • 在配置中调高 silence_threshold(值越高,灵敏度越低)
  • 尝试不同的 STT 模型

语音与 TTS

本页的 Python 依赖命令使用 PM 准备的源码环境。

依赖变更后,请重新激活该 checkout 并重启 Hermes。

Hermes Agent 支持跨所有消息平台的文字转语音(TTS)输出和语音消息转录(STT)。

Nous 订阅用户

如果你拥有付费的 Nous Portal 订阅,OpenAI TTS 可通过 Tool Gateway 使用,无需单独的 OpenAI API 密钥。新安装可运行 hermes setup --portal 登录并一次性开启所有 gateway 工具;已有安装可通过 hermes model 或 hermes tools 选择 Nous Subscription 仅启用 TTS。

文字转语音(TTS)​

支持十个提供商将文字转换为语音:

| | 提供商 | 质量 | 费用 | API 密钥

| Edge TTS(默认) | 良好 | 免费 | 无需

| ElevenLabs | 优秀 | 付费 | ELEVENLABS_API_KEY

| OpenAI TTS | 良好 | 付费 | VOICE_TOOLS_OPENAI_KEY

| MiniMax TTS | 优秀 | 付费 | MINIMAX_API_KEY

| Mistral (Voxtral TTS) | 优秀 | 付费 | MISTRAL_API_KEY

| Google Gemini TTS | 优秀 | 免费额度 | GEMINI_API_KEY

| xAI TTS | 优秀 | 付费 | XAI_API_KEY

| NeuTTS | 良好 | 免费(本地) | 无需

| KittenTTS | 良好 | 免费(本地) | 无需

| Piper | 良好 | 免费(本地) | 无需

平台投递方式​

| | 平台 | 投递方式 | 格式

| Telegram | 语音气泡(内联播放) | Opus .ogg

| Discord | 语音气泡(Opus/OGG),回退为文件附件 | Opus/MP3

| WhatsApp | 音频文件附件 | MP3

| CLI | 保存至 ~/.hermes/audio_cache/ | MP3

配置​

# In ~/.hermes/config.yaml
tts:
  provider: "edge"              # "edge" | "elevenlabs" | "openai" | "minimax" | "mistral" | "gemini" | "xai" | "neutts" | "kittentts" | "piper"
  speed: 1.0                    # Global speed multiplier (provider-specific settings override this)
  edge:
    voice: "en-US-AriaNeural"   # 322 voices, 74 languages
    speed: 1.0                  # Converted to rate percentage (+/-%)
  elevenlabs:
    voice_id: "pNInz6obpgDQGcFmaJgB"  # Adam
    model_id: "eleven_multilingual_v2"
  openai:
    model: "gpt-4o-mini-tts"
    voice: "alloy"              # alloy, echo, fable, onyx, nova, shimmer
    base_url: "https://api.openai.com/v1"  # Override for OpenAI-compatible TTS endpoints
    speed: 1.0                  # 0.25 - 4.0
  minimax:
    model: "speech-2.8-hd"     # speech-2.8-hd (default), speech-2.8-turbo
    voice_id: "English_Graceful_Lady"  # See https://platform.minimax.io/faq/system-voice-id
    speed: 1                    # 0.5 - 2.0
    vol: 1                      # 0 - 10
    pitch: 0                    # -12 - 12
  mistral:
    model: "voxtral-mini-tts-2603"
    voice_id: "c69964a6-ab8b-4f8a-9465-ec0925096ec8"  # Paul - Neutral (default)
  gemini:
    model: "gemini-2.5-flash-preview-tts"  # or gemini-2.5-pro-preview-tts
    voice: "Kore"               # 30 prebuilt voices: Zephyr, Puck, Kore, Enceladus, Gacrux, etc.
  xai:
    voice_id: "eve"             # or a custom voice ID — see docs below
    language: "en"              # ISO 639-1 code
    sample_rate: 24000          # 22050 / 24000 (default) / 44100 / 48000
    bit_rate: 128000            # MP3 bitrate; only applies when codec=mp3
    # base_url: "https://api.x.ai/v1"   # Override via XAI_BASE_URL env var
  neutts:
    ref_audio: ''
    ref_text: ''
    model: neuphonic/neutts-air-q4-gguf
    device: cpu
  kittentts:
    model: KittenML/kitten-tts-nano-0.8-int8   # 25MB int8; also: kitten-tts-micro-0.8 (41MB), kitten-tts-mini-0.8 (80MB)
    voice: Jasper                               # Jasper, Bella, Luna, Bruno, Rosie, Hugo, Kiki, Leo
    speed: 1.0                                  # 0.5 - 2.0
    clean_text: true                            # Expand numbers, currencies, units
  piper:
    voice: en_US-lessac-medium                  # voice name (auto-downloaded) OR absolute path to .onnx
    # voices_dir: ''                            # default: ~/.hermes/cache/piper-voices/
    # use_cuda: false                           # requires onnxruntime-gpu
    # length_scale: 1.0                         # 2.0 = twice as slow
    # noise_scale: 0.667
    # noise_w_scale: 0.8
    # volume: 1.0                               # 0.5 = half as loud
    # normalize_audio: true

KittenTTS 不支持 Intel macOS 和 Windows ARM64:其依赖没有为这些平台发布 onnxruntime 或 PyTorch wheel。在这些平台上选择它会报告该提供商不可用。

速度控制:全局 tts.speed 值默认应用于所有提供商。每个提供商可用自身的 speed 设置覆盖它(例如 tts.openai.speed: 1.5)。提供商级别的速度优先于全局值。默认值为 1.0(正常速度)。

输入长度限制​

每个提供商都有文档记录的单次请求输入字符上限。Hermes 在调用提供商前会截断文本,确保请求不会因长度错误而失败:

| | 提供商 | 默认上限(字符数)

| Edge TTS | 5000

| OpenAI | 4096

| xAI | 15000

| MiniMax | 10000

| Mistral | 4000

| Google Gemini | 5000

| ElevenLabs | 取决于模型(见下文)

| NeuTTS | 2000

| KittenTTS | 2000

ElevenLabs 根据配置的 model_id 选择上限:

| | model_id | 上限(字符数)

| eleven_flash_v2_5 | 40000

| eleven_flash_v2 | 30000

| eleven_multilingual_v2(默认)、eleven_multilingual_v1、eleven_english_sts_v2、eleven_english_sts_v1 | 10000

| eleven_v3、eleven_ttv_v3 | 5000

| 未知模型 | 回退至提供商默认值(10000)

按提供商覆盖,在 TTS 配置的提供商节下使用 max_text_length::

tts:
  openai:
    max_text_length: 8192   # raise or lower the provider cap

仅接受正整数。零、负数、非数字或布尔值将回退至提供商默认值,因此错误的配置不会意外禁用截断。

Telegram 语音气泡与 ffmpeg​

Telegram 语音气泡需要 Opus/OGG 音频格式:

  • OpenAI、ElevenLabs 和 Mistral 原生输出 Opus,无需额外配置
  • Edge TTS(默认)输出 MP3,需要 ffmpeg 进行转换
  • MiniMax TTS 输出 MP3,需要 ffmpeg 转换以在 Telegram 显示语音气泡
  • Google Gemini TTS 输出原始 PCM,使用 ffmpeg 直接编码为 Opus 以在 Telegram 显示语音气泡
  • xAI TTS 输出 MP3,需要 ffmpeg 转换以在 Telegram 显示语音气泡
  • NeuTTS 输出 WAV,同样需要 ffmpeg 转换以在 Telegram 显示语音气泡
  • KittenTTS 输出 WAV,同样需要 ffmpeg 转换以在 Telegram 显示语音气泡
  • Piper 输出 WAV,同样需要 ffmpeg 转换以在 Telegram 显示语音气泡
# Ubuntu/Debian
sudo apt install ffmpeg

# macOS
brew install ffmpeg

# Fedora
sudo dnf install ffmpeg

若未安装 ffmpeg,Edge TTS、MiniMax TTS、NeuTTS、KittenTTS 和 Piper 的音频将作为普通音频文件发送(可播放,但显示为矩形播放器而非语音气泡)。

提示

如果你希望在不安装 ffmpeg 的情况下使用语音气泡,请切换至 OpenAI、ElevenLabs 或 Mistral 提供商。

xAI 自定义声音(声音克隆)​

xAI 支持克隆你的声音并将其用于 TTS。在 xAI Console 中创建自定义声音,然后在配置中设置生成的 voice_id:

tts:
  provider: xai
  xai:
    voice_id: "nlbqfwie"   # your custom voice ID

有关录制、支持格式和限制的详细信息,请参阅 xAI Custom Voices 文档。

Piper(本地,支持 44 种语言)​

Piper 是来自 Open Home Foundation(Home Assistant 维护者)的快速本地神经网络 TTS 引擎。它完全在 CPU 上运行,支持 44 种语言的预训练声音,无需 API 密钥。

通过 hermes tools 安装 → Voice & TTS → Piper。Hermes 通过 PM 请求 piper extra,平台限制仍然适用。

切换至 Piper:

tts:
  provider: piper
  piper:
    voice: en_US-lessac-medium

首次对未在本地缓存的声音进行 TTS 调用时,Hermes 会运行 python -m piper.download_voices <name> 并将模型(约 20-90MB,取决于质量等级)下载至 ~/.hermes/cache/piper-voices/。后续调用将复用已缓存的模型。

选择声音。 完整声音目录 涵盖英语、西班牙语、法语、德语、意大利语、荷兰语、葡萄牙语、俄语、波兰语、土耳其语、中文、阿拉伯语、印地语等——每种语言均有 x_low / low / medium / high 质量等级。可在 rhasspy.github.io/piper-samples 试听声音样本。

使用预下载的声音。 将 tts.piper.voice 设置为以 .onnx 结尾的绝对路径:

tts:
  piper:
    voice: /path/to/my-custom-voice.onnx

高级参数(tts.piper.length_scale / noise_scale / noise_w_scale / volume / normalize_audio、use_cuda)与 Piper 的 SynthesisConfig 一一对应。在较旧的 piper-tts 版本上这些参数会被忽略。

自定义命令提供商​

如果你想使用的 TTS 引擎未被原生支持(VoxCPM、MLX-Kokoro、XTTS CLI、声音克隆脚本,或任何其他暴露 CLI 的引擎),你可以将其作为命令类型提供商接入,无需编写任何 Python 代码。Hermes 将输入文本写入临时 UTF-8 文件,运行你的 shell 命令,并读取命令生成的音频文件。

在 tts.providers.<name> 下声明一个或多个提供商,并通过 tts.provider: <name> 在它们之间切换——与切换 edge 和 openai 等内置提供商的方式相同。

tts:
  provider: voxcpm                 # pick any name under tts.providers
  providers:
    voxcpm:
      type: command
      command: "voxcpm --ref ~/voice.wav --text-file {input_path} --out {output_path}"
      output_format: mp3
      timeout: 180
      voice_compatible: true       # try to deliver as a Telegram voice bubble

    mlx-kokoro:
      type: command
      command: "python -m mlx_kokoro --in {input_path} --out {output_path} --voice {voice}"
      voice: af_sky
      output_format: wav

    piper-custom:                  # native Piper also supports custom .onnx via tts.piper.voice
      type: command
      command: "piper -m /path/to/custom.onnx -f {output_path} < {input_path}"
      output_format: wav

示例:Doubao(中文 seed-tts-2.0)​

如需通过字节跳动的 seed-tts-2.0 双向流式 API 实现高质量中文 TTS,请安装 doubao-speech PyPI 包并将其作为命令提供商接入:

请在独立的工具环境中安装此外部命令提供商,不要修改 Hermes 的 Python 环境。

确保其可执行文件可通过 PATH 访问。

pip install doubao-speech
export VOLCENGINE_APP_ID="your-app-id"
export VOLCENGINE_ACCESS_TOKEN="your-access-token"
tts:
  provider: doubao
  providers:
    doubao:
      type: command
      command: "doubao-speech say --text-file {input_path} --out {output_path}"
      output_format: mp3
      max_text_length: 1024
      timeout: 30

凭据来自你的 shell 环境(VOLCENGINE_APP_ID / VOLCENGINE_ACCESS_TOKEN)或 ~/.doubao-speech/config.yaml。通过在命令中添加 --voice zh-female-warm(或 doubao-speech list-voices 中的任何其他别名)来选择声音。doubao-speech 还内置了流式 ASR——有关 Hermes 集成,请参阅下方的 STT 章节。源码和完整文档:github.com/Hypnus-Yuan/doubao-speech。

占位符​

你的命令模板可以引用以下占位符。Hermes 在渲染时会替换它们,并根据上下文(裸值 / 单引号 / 双引号)对每个值进行 shell 转义,因此包含空格和其他 shell 敏感字符的路径是安全的。

| | 占位符 | 含义

| {input_path} | Hermes 写入的临时 UTF-8 文本文件路径

| {text_path} | {input_path} 的别名

| {output_path} | 命令必须写入音频的路径

| {format} | mp3 / wav / ogg / flac

| {voice} | tts.providers.<name>.voice,未设置时为空

| {model} | tts.providers.<name>.model

| {speed} | 解析后的速度倍率(提供商级别或全局)

使用 {{ 和 }} 表示字面大括号。

可选键​

| | 键 | 默认值 | 含义

| timeout | 120 | 秒数;超时后进程树将被终止(Unix killpg,Windows taskkill /T)。

| output_format | mp3 | mp3 / wav / ogg / flac 之一。若 Hermes 选择路径,则从输出扩展名自动推断。

| voice_compatible | false | 为 true 时,Hermes 通过 ffmpeg 将 MP3/WAV 输出转换为 Opus/OGG,使 Telegram 渲染语音气泡。

| max_text_length | 5000 | 渲染命令前,输入将被截断至此长度。

| voice / model | 空 | 仅作为占位符值传递给命令。

行为说明​

  • 内置名称始终优先。 tts.providers.openai 条目永远不会覆盖原生 OpenAI 提供商,因此任何用户配置都无法静默替换内置提供商。
  • 默认投递方式为文档。 命令提供商在所有平台上均以普通音频附件投递。通过 voice_compatible: true 按提供商选择加入语音气泡投递。
  • 命令失败会暴露给 Agent。 非零退出码、空输出或超时均会返回包含命令 stderr/stdout 的错误,便于你从对话中调试提供商。
  • 设置了 command: 时,type: command 为默认值。 显式写出 type: command 是良好实践,但非必须;包含非空 command 字符串的条目会被视为命令提供商。
  • {input_path} / {text_path} 可互换。 使用在你的命令中读起来更自然的那个。

安全性​

命令类型提供商会以你的用户权限运行你配置的任何 shell 命令。Hermes 会对占位符值进行转义并强制执行配置的超时,但命令模板本身是受信任的本地输入——请像对待 PATH 中的 shell 脚本一样对待它。

Python 插件提供商​

对于无法用单个 shell 命令表达的 TTS 引擎——没有 CLI 的 Python SDK、流式引擎、声音列表 API、OAuth 刷新认证——可通过 ctx.register_tts_provider() 注册 Python 插件。该插件与自定义命令提供商注册表共存(不替换);选择适合你引擎的接入方式。

如何选择​

| | 你的后端具有… | 使用

| 单个 CLI,从文件/stdin 读取文本并将音频写入文件/stdout | 命令提供商(无需 Python)

| 两三个通过 shell 管道串联的 CLI | 命令提供商

| 仅有 Python SDK,没有 CLI | 插件

| 你希望分块投递的流式字节(生成中的语音气泡) | 插件(覆盖 stream())

| hermes setup 使用的声音列表 API | 插件(覆盖 list_voices())

| OAuth 刷新流程(非静态 bearer token) | 插件

内置提供商始终优先,命令提供商优先于同名插件——因此插件可以安全地注册任何非内置名称,无需担心覆盖现有配置。

最小插件​

将以下内容放入 ~/.hermes/plugins/my-tts/:

plugin.yaml:

name: my-tts
version: 0.1.0
description: "My custom Python TTS backend"

__init__.py:

from agent.tts_provider import TTSProvider


class MyTTSProvider(TTSProvider):
    @property
    def name(self) -> str:
        return "my-tts"  # what tts.provider matches against

    @property
    def display_name(self) -> str:
        return "My Custom TTS"

    def is_available(self) -> bool:
        # Return False when credentials/deps are missing — picker skips
        # this row but the dispatcher still routes here on explicit config.
        import os
        return bool(os.environ.get("MY_TTS_API_KEY"))

    def synthesize(self, text, output_path, *, voice=None, model=None,
                   speed=None, format="mp3", **extra) -> str:
        # Write audio bytes to output_path, return the path.
        # Raise on failure — the dispatcher converts exceptions to a
        # standard error envelope.
        import my_tts_sdk
        client = my_tts_sdk.Client()
        audio_bytes = client.synthesize(text=text, voice=voice or "default")
        with open(output_path, "wb") as f:
            f.write(audio_bytes)
        return output_path


def register(ctx):
    ctx.register_tts_provider(MyTTSProvider())

启用它(hermes plugins enable my-tts),将 tts.provider 指向它(在 config.yaml 中设置 tts.provider: my-tts),text_to_speech 工具将通过你的插件路由。

可选 hook​

在你的提供商类上覆盖以下方法以获得更丰富的集成:

  • list_voices() → 返回 {id, display, language, gender, preview_url} 字典列表,显示在 hermes tools 中。
  • list_models() → 返回 {id, display, languages, max_text_length} 字典列表。
  • get_setup_schema() → 返回 {name, badge, tag, env_vars: [{key, prompt, url}]} 以驱动 hermes tools / hermes setup 中的选择器行。若不提供,插件仍可正常工作,但其在选择器中的行信息会很简略。
  • stream(text, *, voice, model, format, **extra) → 迭代器,产出音频字节用于流式投递(默认抛出 NotImplementedError)。
  • voice_compatible 属性 → 若你的输出与 Opus 兼容且 gateway 应将其作为语音气泡投递,则设为 True(默认 False = 普通音频附件)。

完整的抽象基类(含文档字符串)请参阅 agent/tts_provider.py。

语音消息转录(STT)​

在 Telegram、Discord、WhatsApp、Slack 或 Signal 上发送的语音消息会被自动转录并作为文本注入对话。Agent 将转录内容视为普通文本。

| | 提供商 | 质量 | 费用 | API 密钥

| 本地 Whisper(默认) | 良好 | 免费 | 无需

| Groq Whisper API | 良好至最佳 | 免费额度 | GROQ_API_KEY

| OpenAI Whisper API | 良好至最佳 | 付费 | VOICE_TOOLS_OPENAI_KEY 或 OPENAI_API_KEY

零配置

安装了 faster-whisper 后,本地转录即可开箱即用。若不可用,Hermes 也可使用常见安装位置(如 /opt/homebrew/bin)的本地 whisper CLI,或通过 HERMES_LOCAL_STT_COMMAND 指定的自定义命令。

配置​

# In ~/.hermes/config.yaml
stt:
  provider: "local"           # "local" | "groq" | "openai" | "mistral" | "xai"
  local:
    model: "base"             # tiny, base, small, medium, large-v3, turbo
  openai:
    model: "whisper-1"        # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe, gpt-transcribe
  mistral:
    model: "voxtral-mini-latest"  # voxtral-mini-latest, voxtral-mini-2602
  xai:
    model: "grok-voice-transcribe-2.0"  # 或 grok-voice-transcribe-1.0

提供商详情​

本地(faster-whisper) — 通过 faster-whisper 在本地运行 Whisper。默认使用 CPU,有 GPU 时使用 GPU。模型大小:

| | 模型 | 大小 | 速度 | 质量

| tiny | ~75 MB | 最快 | 基础

| base | ~150 MB | 快 | 良好(默认)

| small | ~500 MB | 中等 | 较好

| medium | ~1.5 GB | 较慢 | 优秀

| large-v3 | ~3 GB | 最慢 | 最佳

Groq API — 需要 GROQ_API_KEY。当你需要免费托管 STT 选项时,是良好的云端备选方案。

OpenAI API — 优先使用 VOICE_TOOLS_OPENAI_KEY,回退至 OPENAI_API_KEY。支持 whisper-1、gpt-4o-mini-transcribe、gpt-4o-transcribe 和 gpt-transcribe。

Mistral API(Voxtral Transcribe) — 需要 MISTRAL_API_KEY。使用 Mistral 的 Voxtral Transcribe 模型。支持 13 种语言、说话人分离和词级时间戳。通过 cd ~/.hermes/hermes-agent && python -c "import pm; pm.sync_venv(['mistral'], explicit=True)" 安装。

xAI Grok STT — 需要 XAI_API_KEY。以 multipart/form-data 格式发送至 https://api.x.ai/v1/stt。如果你已在使用 xAI 进行聊天或 TTS 并希望一个 API 密钥搞定一切,这是个好选择。自动检测顺序将其排在 Groq 之后——显式设置 stt.provider: xai 可强制使用。

自定义本地 CLI 回退 — 若你希望 Hermes 直接调用本地转录命令,请设置 HERMES_LOCAL_STT_COMMAND。命令模板支持 {input_path}、{output_dir}、{language} 和 {model} 占位符。你的命令必须在 {output_dir} 下某处写入 .txt 转录文件。

示例:Doubao / Volcengine ASR​

如果你使用 doubao-speech 进行 Doubao TTS(见上文),同一个包也可通过本地命令 STT 接口处理语音转文字:

请在独立的工具环境中安装此外部命令提供商,不要修改 Hermes 的 Python 环境。

确保其可执行文件可通过 PATH 访问。

pip install doubao-speech
export VOLCENGINE_APP_ID="your-app-id"
export VOLCENGINE_ACCESS_TOKEN="your-access-token"
export HERMES_LOCAL_STT_COMMAND='doubao-speech transcribe {input_path} --out {output_dir}/transcript.txt'
stt:
  provider: local_command

Hermes 将传入的语音消息写入 {input_path},运行命令,并读取 {output_dir} 下生成的 .txt 文件。语言由 Volcengine bigmodel 端点自动检测。

回退行为​

若配置的提供商不可用,Hermes 会自动回退:

  • 本地 faster-whisper 不可用 → 在云端提供商之前尝试本地 whisper CLI 或 HERMES_LOCAL_STT_COMMAND
  • 未设置 Groq 密钥 → 回退至本地转录,然后是 OpenAI
  • 未设置 OpenAI 密钥 → 回退至本地转录,然后是 Groq
  • 未设置 Mistral 密钥/SDK → 在自动检测中跳过;回退至下一个可用提供商
  • 无可用提供商 → 语音消息直接传递,并向用户给出准确说明

文生图(Image Generation)

Hermes Agent 通过 FAL.ai 根据文字提示生成图像。默认内置 8 个模型,在速度、画质与成本上各有取舍。当前模型可通过 hermes tools 配置,并持久化在 config.yaml。

支持的模型​

| | 模型 | 速度 | 特点 | 参考价格

| fal-ai/flux-2/klein/9b (默认) | <1s | 快、文字清晰 | $0.006/MP

| fal-ai/flux-2-pro | ~6s | 棚拍级写实 | $0.03/MP

| fal-ai/z-image/turbo | ~2s | 中英双语,6B | $0.005/MP

| fal-ai/nano-banana-pro | ~8s | Gemini 3 Pro、推理与文字渲染 | $0.15/张(1K)

| fal-ai/gpt-image-1.5 | ~15s | 强指令遵循 | $0.034/张

| fal-ai/ideogram/v3 | ~5s | 排版最佳 | $0.03–0.09/张

| fal-ai/recraft/v4/pro/text-to-image | ~8s | 设计 / 品牌系统 / 可交付生产 | $0.25/张

| fal-ai/qwen-image | ~12s | 偏 LLM 式、复杂文字 | $0.02/MP

价格为撰写时的 FAL 官方口径;最新计费请以 fal.ai 为准。

配置​

Nous 订阅用户

若你持有付费 Nous Portal 订阅,可通过 Tool Gateway 使用文生图,无需 FAL_KEY。模型选择在「直连 FAL」与「订阅网关」两条路径下保持一致。

若托管网关对某一模型返回 HTTP 4xx,通常表示该模型尚未在 Portal 侧代理——智能体会给出处理建议(例如配置 FAL_KEY 直连,或换用其他模型)。

获取 FAL API Key​

  • 在 fal.ai 注册
  • 在控制台生成 API Key

配置并选择模型​

执行:

hermes tools

进入 🎨 Image Generation,选择后端(Nous Subscription 或 FAL.ai),随后在表格中用方向键选择模型,回车确认:

  Model                          Speed    Strengths                    Price
  fal-ai/flux-2/klein/9b         <1s      Fast, crisp text             $0.006/MP   ← currently in use
  fal-ai/flux-2-pro              ~6s      Studio photorealism          $0.03/MP
  fal-ai/z-image/turbo           ~2s      Bilingual EN/CN, 6B          $0.005/MP
  ...

选择会写入 config.yaml:

image_gen:
  provider: fal                 # 选 Nous Subscription 时为 `nous`
  model: fal-ai/flux-2/klein/9b

image_gen.provider 是唯一的选择键:nous 走托管网关,厂商名(如 fal)走直连。运行时始终按该选择路由——provider: nous 时 .env 里的 FAL_KEY 会被忽略;provider: fal 而缺少 FAL_KEY 会直接报错并提示运行 hermes tools,不会静默回退。(旧的 use_gateway 键已废弃,读取时 true 等同于 nous。)

GPT-Image 画质档位​

fal-ai/gpt-image-1.5 的请求画质固定为 medium(约 1024×1024 下 $0.034/张)。面向用户不开放 low / high 档位,以便 Nous Portal 侧计费在全体用户间更可预期(档位价差约 22×)。若需要更便宜的 GPT-Image 路线,请换其他模型;若追求更高画质,可考虑 Klein 9B 或同类 Imagen 系模型。

使用方式​

对智能体暴露的 schema 刻意保持简单——具体行为由你在本机的配置决定:

Generate an image of a serene mountain landscape with cherry blossoms
Create a square portrait of a wise old owl — use the typography model
Make me a futuristic cityscape, landscape orientation

宽高比​

从智能体视角,三个宽高比词对所有模型通用;内部会映射到各模型原生参数:

| | 智能体输入 | image_size(flux/z-image/qwen/recraft/ideogram) | aspect_ratio(nano-banana-pro) | image_size(gpt-image)

| landscape | landscape_16_9 | 16:9 | 1536x1024

| square | square_hd | 1:1 | 1024x1024

| portrait | portrait_16_9 | 9:16 | 1024x1536

该映射在 _build_fal_payload() 中完成,智能体代码无需了解各模型 schema 差异。

自动超分(Upscale)​

超分默认关闭(所有模型)。现代图像模型原生输出即为最佳质量,而可用的超分器属于创意增强器(扩散重绘),会细微改动内容——损伤文字渲染、人脸与细节。仅当智能体显式传入 upscale: true 时才会执行 FAL Clarity Upscaler 超分。

超分启用时的主要参数:

| | 项 | 值

| 放大倍数 | 2×

| Creativity | 0.35

| Resemblance | 0.6

| Guidance scale | 4

| Inference steps | 18

若超分失败(网络、限流等),会自动回退为返回原始图像。

内部流程概要​

  • 模型解析 — _resolve_fal_model() 读取 config.yaml 的 image_gen.model,否则看 FAL_IMAGE_MODEL 环境变量,再否则默认 fal-ai/flux-2/klein/9b。
  • 构造请求体 — _build_fal_payload() 将 aspect_ratio 转为各模型枚举或字面量,合并默认参数与调用方覆盖,并按 supports 白名单过滤非法字段。
  • 提交 — _submit_fal_request() 根据凭据走直连 FAL 或 Nous 托管网关。
  • 超分 — 仅当调用显式传入 upscale: true 时执行;所有模型目录默认关闭。
  • 交付 — 最终图像 URL 返回给智能体,并发出 MEDIA:<url>,由各平台适配器转为原生媒体消息。

调试​

打开调试日志:

export IMAGE_TOOLS_DEBUG=true

日志写入 ./logs/image_tools_debug_<session_id>.json,包含每次调用的模型、参数、耗时与错误信息。

各平台展示​

| | 平台 | 行为

| CLI | 图像 URL 以 Markdown ![](url) 打印,可点击打开

| Telegram | 以图片消息发送,附提示词为说明

| Discord | 嵌入消息

| Slack | URL 由 Slack 展开预览

| WhatsApp | 媒体消息

| 其他 | 纯文本中的 URL

限制​

  • 需要 FAL 凭据(直连 FAL_KEY 或 Nous 订阅网关)
  • 仅文生图 — 不支持局部重绘、图生图或编辑类工作流
  • 临时 URL — FAL 托管链接会在数小时至数天后过期;请自行落盘保存
  • 按模型能力裁剪 — 部分模型不支持 seed、num_inference_steps 等;supports 会静默丢弃不支持的参数,属预期行为

上下文文件

Hermes Agent 会自动发现并加载上下文文件,以塑造其行为方式。部分文件属于项目本地文件,从工作目录中发现。SOUL.md 现在对整个 Hermes 实例全局生效,仅从 HERMES_HOME 加载。

支持的上下文文件​

| | 文件 | 用途 | 发现方式

| .hermes.md / HERMES.md | 项目指令(最高优先级) | 向上遍历至 git 根目录

| AGENTS.md | 项目指令、规范、架构说明 | 启动时的 CWD 及子目录(渐进式)

| CLAUDE.md | Claude Code 上下文文件(同样支持检测) | 启动时的 CWD 及子目录(渐进式)

| SOUL.md | 当前 Hermes 实例的全局个性与语气定制 | 仅 HERMES_HOME/SOUL.md

| .cursorrules | Cursor IDE 编码规范 | 仅 CWD

| .cursor/rules/*.mdc | Cursor IDE 规则模块 | 仅 CWD

优先级系统

每次会话仅加载一种项目上下文类型(先匹配先生效):.hermes.md → AGENTS.md → CLAUDE.md → .cursorrules。SOUL.md 始终作为 agent 身份独立加载(插槽 #1)。

AGENTS.md​

AGENTS.md 是主要的项目上下文文件。它告知 agent 项目的结构、需要遵循的规范以及任何特殊指令。

渐进式子目录发现​

会话启动时,Hermes 将工作目录中的 AGENTS.md 加载到系统 prompt(提示词)中。在会话期间,当 agent 通过 read_file、terminal、search_files 等工具导航进入子目录时,它会渐进式发现这些目录中的上下文文件,并在其变得相关的时刻将其注入对话。

my-project/
├── AGENTS.md              ← 启动时加载(系统 prompt)
├── frontend/
│   └── AGENTS.md          ← agent 读取 frontend/ 文件时发现
├── backend/
│   └── AGENTS.md          ← agent 读取 backend/ 文件时发现
└── shared/
    └── AGENTS.md          ← agent 读取 shared/ 文件时发现

与启动时加载所有内容相比,此方式有两个优势:

  • 避免系统 prompt 膨胀 — 子目录提示仅在需要时出现
  • 保留 prompt 缓存 — 系统 prompt 在各轮次间保持稳定

每个子目录在每次会话中最多检查一次。发现机制同样会向上遍历父目录,因此读取 backend/src/main.py 时,即使 backend/src/ 没有自己的上下文文件,也会发现 backend/AGENTS.md。

信息

子目录上下文文件与启动时的上下文文件经过相同的安全扫描。恶意文件会被拦截。

AGENTS.md 示例​

# Project Context

This is a Next.js 14 web application with a Python FastAPI backend.

## Architecture
- Frontend: Next.js 14 with App Router in `/frontend`
- Backend: FastAPI in `/backend`, uses SQLAlchemy ORM
- Database: PostgreSQL 16
- Deployment: Docker Compose on a Hetzner VPS

## Conventions
- Use TypeScript strict mode for all frontend code
- Python code follows PEP 8, use type hints everywhere
- All API endpoints return JSON with `{data, error, meta}` shape
- Tests go in `__tests__/` directories (frontend) or `tests/` (backend)

## Important Notes
- Never modify migration files directly — use Alembic commands
- The `.env.local` file has real API keys, don't commit it
- Frontend port is 3000, backend is 8000, DB is 5432

SOUL.md​

SOUL.md 控制 agent 的个性、语气和沟通风格。完整详情请参阅个性页面。

位置:

  • ~/.hermes/SOUL.md
  • 或 $HERMES_HOME/SOUL.md(若使用自定义主目录运行 Hermes)

重要说明:

  • 若 SOUL.md 尚不存在,Hermes 会自动生成一个默认文件
  • Hermes 仅从 HERMES_HOME 加载 SOUL.md
  • Hermes 不会在工作目录中探测 SOUL.md
  • 若文件为空,SOUL.md 中的内容不会添加到 prompt
  • 若文件有内容,内容在扫描和截断后原样注入

.cursorrules​

Hermes 兼容 Cursor IDE 的 .cursorrules 文件和 .cursor/rules/*.mdc 规则模块。若这些文件存在于项目根目录,且未找到更高优先级的上下文文件(.hermes.md、AGENTS.md 或 CLAUDE.md),则将其作为项目上下文加载。

这意味着使用 Hermes 时,现有的 Cursor 规范会自动生效。

上下文文件的加载方式​

启动时(系统 prompt)​

上下文文件由 agent/prompt_builder.py 中的 build_context_files_prompt() 加载:

  • 扫描工作目录 — 依次检查 .hermes.md → AGENTS.md → CLAUDE.md → .cursorrules(先匹配先生效)
  • 读取内容 — 以 UTF-8 文本读取每个文件
  • 安全扫描 — 检查内容是否存在 prompt 注入模式
  • 截断 — 超过 20,000 个字符的文件进行首尾截断(70% 头部,20% 尾部,中间插入标记)
  • 组装 — 所有部分合并在 # Project Context 标题下
  • 注入 — 组装后的内容添加到系统 prompt

会话期间(渐进式发现)​

agent/subdirectory_hints.py 中的 SubdirectoryHintTracker 监视工具调用参数中的文件路径:

  • 路径提取 — 每次工具调用后,从参数(path、workdir、shell 命令)中提取文件路径
  • 祖先目录遍历 — 检查该目录及最多 5 个父目录(跳过已访问的目录)
  • 提示加载 — 若发现 AGENTS.md、CLAUDE.md 或 .cursorrules,则加载(每个目录先匹配先生效)
  • 安全扫描 — 与启动文件相同的 prompt 注入扫描
  • 截断 — 每个文件最多 8,000 个字符
  • 注入 — 追加到工具结果中,使模型在上下文中自然看到

最终 prompt 部分大致如下:

# Project Context

The following project context files have been loaded and should be followed:

## AGENTS.md

[Your AGENTS.md content here]

## .cursorrules

[Your .cursorrules content here]

[Your SOUL.md content here]

注意,SOUL 内容直接插入,不带额外的包装文本。

安全性:Prompt 注入防护​

所有上下文文件在被纳入之前都会扫描潜在的 prompt 注入。扫描器检查以下内容:

  • 指令覆盖尝试:「ignore previous instructions」、「disregard your rules」
  • 欺骗模式:「do not tell the user」
  • 系统 prompt 覆盖:「system prompt override」
  • 隐藏 HTML 注释:<!-- ignore instructions -->
  • 隐藏 div 元素:<div style="display:none">
  • 凭据窃取:curl ... $API_KEY
  • 密钥文件访问:cat .env、cat credentials
  • 不可见字符:零宽空格、双向覆盖字符、词连接符

若检测到任何威胁模式,该文件将被拦截:

[BLOCKED: AGENTS.md contained potential prompt injection (prompt_injection). Content not loaded.]

注意

此扫描器可防范常见注入模式,但不能替代对上下文文件的人工审查。对于非本人编写的共享仓库,请务必验证 AGENTS.md 的内容。

大小限制​

| | 限制 | 值

| 每个文件最大字符数 | 20,000(约 7,000 个 token)

| 头部截断比例 | 70%

| 尾部截断比例 | 20%

| 截断标记 | 10%(显示字符数并建议使用文件工具)

当文件超过 20,000 个字符时,截断提示如下:

[...truncated AGENTS.md: kept 14000+4000 of 25000 chars. Use file tools to read the full file.]

有效使用上下文文件的技巧​

AGENTS.md 最佳实践

  • 保持简洁 — 远低于 20K 字符;agent 每轮都会读取
  • 使用标题结构 — 用 ## 分节描述架构、规范、重要说明
  • 包含具体示例 — 展示首选代码模式、API 结构、命名规范
  • 说明禁止事项 — 例如「不得直接修改迁移文件」
  • 列出关键路径和端口 — agent 在执行终端命令时会用到
  • 随项目演进更新 — 过时的上下文比没有上下文更糟

子目录上下文​

对于 monorepo,在嵌套的 AGENTS.md 文件中放置子目录专属指令:

<!-- frontend/AGENTS.md -->
# Frontend Context

- Use `pnpm` not `npm` for package management
- Components go in `src/components/`, pages in `src/app/`
- Use Tailwind CSS, never inline styles
- Run tests with `pnpm test`
<!-- backend/AGENTS.md -->
# Backend Context

- Use `poetry` for dependency management
- Run the dev server with `poetry run uvicorn main:app --reload`
- All endpoints need OpenAPI docstrings
- Database models are in `models/`, schemas in `schemas/`

Event Hooks

Hermes 有四套 hook 系统,可在关键生命周期节点运行自定义代码:

| | 系统 | 注册方式 | 运行环境 | 使用场景

| Gateway hooks | ~/.hermes/hooks/ 下的 HOOK.yaml + handler.py | 仅 Gateway | 日志、告警、webhook

| Plugin hooks | 插件中的 ctx.register_hook() | CLI + Gateway | 工具拦截、指标采集、护栏

| Shell hooks | ~/.hermes/config.yaml 中 hooks: 块指向的 shell 脚本 | CLI + Gateway | 用于阻断、自动格式化、上下文注入的即插即用脚本

| Outbound webhooks | ~/.hermes/config.yaml 中的 hooks.outbound: 列表 | CLI + Gateway | 将签名后的生命周期事件推送到外部 HTTP endpoint

Hook 回调错误会被隔离并记录,不会导致 agent 崩溃。但 hook 并非全是被动观察者:指令/控制类 hook 可改变流程,transform 可替换内容,shell pre_tool_call 还能阻断或在失败时关闭执行。

Gateway Event Hooks​

Gateway hooks 在 gateway 运行期间(Telegram、Discord、Slack、WhatsApp、Teams)自动触发,不会阻塞主 agent 管道。

创建 Hook​

每个 hook 是 ~/.hermes/hooks/ 下的一个目录,包含两个文件:

~/.hermes/hooks/
└── my-hook/
    ├── HOOK.yaml      # 声明要监听的事件
    └── handler.py     # Python 处理函数

HOOK.yaml​

name: my-hook
description: Log all agent activity to a file
events:
  - agent:start
  - agent:end
  - agent:step

events 列表决定哪些事件会触发你的处理器。可以订阅任意事件组合,包括 command:* 这样的通配符。

handler.py​

import json
from datetime import datetime
from pathlib import Path

LOG_FILE = Path.home() / ".hermes" / "hooks" / "my-hook" / "activity.log"

async def handle(event_type: str, context: dict):
    """Called for each subscribed event. Must be named 'handle'."""
    entry = {
        "timestamp": datetime.now().isoformat(),
        "event": event_type,
        **context,
    }
    with open(LOG_FILE, "a") as f:
        f.write(json.dumps(entry) + "\n")

处理器规则:

  • 必须命名为 handle
  • 接收 event_type(字符串)和 context(字典)
  • 可以是 async def 或普通 def——两者均可
  • 错误会被捕获并记录,不会导致 agent 崩溃

可用事件​

| | 事件 | 触发时机 | Context 键

| gateway:startup | Gateway 进程启动 | platforms(活跃平台名称列表)

| session:start | 新消息会话创建 | platform、user_id、session_id、session_key

| session:end | 会话结束(重置前) | platform、user_id、session_key

| session:reset | 用户执行 /new 或 /reset | platform、user_id、session_key

| agent:start | Agent 开始处理消息 | platform、user_id、session_id、message

| agent:step | 工具调用循环的每次迭代 | platform、user_id、session_id、iteration、tool_names

| agent:end | Agent 完成处理 | platform、user_id、session_id、message、response

| command:* | 任意斜杠命令执行 | platform、user_id、command、args

通配符匹配​

注册了 command:* 的处理器会在任何 command: 事件(command:model、command:reset 等)触发时执行。通过单个订阅即可监控所有斜杠命令。

示例​

Telegram 长任务告警​

当 agent 执行超过 10 步时向自己发送消息:

# ~/.hermes/hooks/long-task-alert/HOOK.yaml
name: long-task-alert
description: Alert when agent is taking many steps
events:
  - agent:step
# ~/.hermes/hooks/long-task-alert/handler.py
import os
import httpx

THRESHOLD = 10
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
CHAT_ID = os.getenv("TELEGRAM_HOME_CHANNEL")

async def handle(event_type: str, context: dict):
    iteration = context.get("iteration", 0)
    if iteration == THRESHOLD and BOT_TOKEN and CHAT_ID:
        tools = ", ".join(context.get("tool_names", []))
        text = f"⚠️ Agent has been running for {iteration} steps. Last tools: {tools}"
        async with httpx.AsyncClient() as client:
            await client.post(
                f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
                json={"chat_id": CHAT_ID, "text": text},
            )

命令使用日志记录器​

追踪哪些斜杠命令被使用:

# ~/.hermes/hooks/command-logger/HOOK.yaml
name: command-logger
description: Log slash command usage
events:
  - command:*
# ~/.hermes/hooks/command-logger/handler.py
import json
from datetime import datetime
from pathlib import Path

LOG = Path.home() / ".hermes" / "logs" / "command_usage.jsonl"

def handle(event_type: str, context: dict):
    LOG.parent.mkdir(parents=True, exist_ok=True)
    entry = {
        "ts": datetime.now().isoformat(),
        "command": context.get("command"),
        "args": context.get("args"),
        "platform": context.get("platform"),
        "user": context.get("user_id"),
    }
    with open(LOG, "a") as f:
        f.write(json.dumps(entry) + "\n")

会话开始 Webhook​

新会话时 POST 到外部服务:

# ~/.hermes/hooks/session-webhook/HOOK.yaml
name: session-webhook
description: Notify external service on new sessions
events:
  - session:start
  - session:reset
# ~/.hermes/hooks/session-webhook/handler.py
import httpx

WEBHOOK_URL = "https://your-service.example.com/hermes-events"

async def handle(event_type: str, context: dict):
    async with httpx.AsyncClient() as client:
        await client.post(WEBHOOK_URL, json={
            "event": event_type,
            **context,
        }, timeout=5)

教程:BOOT.md——每次 Gateway 启动时运行启动检查清单​

这是社区中流行的一种模式:在 ~/.hermes/BOOT.md 放置一个 Markdown 检查清单,让 agent 在每次 gateway 启动时执行一次。适用于"每次启动时检查隔夜 cron 失败情况,若有失败则在 Discord 上通知我",或"汇总过去 24 小时的 deploy.log 并发布到 Slack #ops"等场景。

本教程展示如何以用户自定义 hook 的方式自行构建。Hermes 不内置 BOOT.md hook——你可以精确配置自己想要的行为。

我们要构建什么​

  • 在 ~/.hermes/BOOT.md 放置一个包含自然语言启动指令的文件。
  • 一个监听 gateway:startup 的 gateway hook,它会生成一个一次性 agent,使用 gateway 已解析的模型和凭据,执行 BOOT.md 中的指令。
  • 一个 [SILENT] 约定,让 agent 在没有内容需要汇报时选择不发送消息。

第一步:编写检查清单​

创建 ~/.hermes/BOOT.md。像给人类助手下达指令一样编写:

# Startup Checklist

1. Run `hermes cron list` and check if any scheduled jobs failed overnight.
2. If any failed, send a summary to Discord #ops using the `send_message` tool.
3. Check if `/opt/app/deploy.log` has any ERROR lines from the last 24 hours. If yes, summarize them and include in the same Discord message.
4. If nothing went wrong, reply with only `[SILENT]` so no message is sent.

Agent 将此内容作为 prompt(提示词)的一部分,因此任何可以用自然语言描述的内容都可以——工具调用、shell 命令、发送消息、汇总文件。

第二步:创建 hook​

~/.hermes/hooks/boot-md/
├── HOOK.yaml
└── handler.py

~/.hermes/hooks/boot-md/HOOK.yaml

name: boot-md
description: Run ~/.hermes/BOOT.md on gateway startup
events:
  - gateway:startup

~/.hermes/hooks/boot-md/handler.py

"""Run ~/.hermes/BOOT.md on every gateway startup."""

import logging
import threading
from pathlib import Path

logger = logging.getLogger("hooks.boot-md")

BOOT_FILE = Path.home() / ".hermes" / "BOOT.md"


def _build_prompt(content: str) -> str:
    return (
        "You are running a startup boot checklist. Follow the instructions "
        "below exactly.\n\n"
        "---\n"
        f"{content}\n"
        "---\n\n"
        "Execute each instruction. Use the send_message tool to deliver any "
        "messages to platforms like Discord or Slack.\n"
        "If nothing needs attention and there is nothing to report, reply "
        "with ONLY: [SILENT]"
    )


def _run_boot_agent(content: str) -> None:
    """Spawn a one-shot agent and execute the checklist.

    Uses the gateway's resolved model and runtime credentials so this works
    against custom endpoints, aggregators, and OAuth-based providers alike.
    """
    try:
        from gateway.run import _resolve_gateway_model, _resolve_runtime_agent_kwargs
        from run_agent import AIAgent

        agent = AIAgent(
            model=_resolve_gateway_model(),
            **_resolve_runtime_agent_kwargs(),
            platform="gateway",
            quiet_mode=True,
            skip_context_files=True,
            skip_memory=True,
            max_iterations=20,
        )
        result = agent.run_conversation(_build_prompt(content))
        response = result.get("final_response", "")
        if response and "[SILENT]" not in response:
            logger.info("boot-md completed: %s", response[:200])
        else:
            logger.info("boot-md completed (nothing to report)")
    except Exception as e:
        logger.error("boot-md agent failed: %s", e)


async def handle(event_type: str, context: dict) -> None:
    if not BOOT_FILE.exists():
        return
    content = BOOT_FILE.read_text(encoding="utf-8").strip()
    if not content:
        return

    logger.info("Running BOOT.md (%d chars)", len(content))

    # Background thread so gateway startup isn't blocked on a full agent turn.
    thread = threading.Thread(
        target=_run_boot_agent,
        args=(content,),
        name="boot-md",
        daemon=True,
    )
    thread.start()

两个关键行:

  • _resolve_gateway_model() 读取 gateway 当前配置的模型。
  • _resolve_runtime_agent_kwargs() 以与普通 gateway 轮次相同的方式解析 provider 凭据——包括 API 密钥、base URL、OAuth token 和凭据池。

若不使用这两行,裸 AIAgent() 会回退到内置默认值,并在任何非默认端点上返回 401。

第三步:测试​

重启 gateway:

hermes gateway restart

查看日志:

hermes logs --follow --level INFO | grep boot-md

你应该看到 Running BOOT.md (N chars),随后是 boot-md completed: ...(agent 执行内容的摘要)或 boot-md completed (nothing to report)(agent 回复了 [SILENT])。

删除 ~/.hermes/BOOT.md 即可禁用检查清单——hook 保持加载状态,但在文件不存在时会静默跳过。

扩展此模式​

  • 感知调度的检查清单: 在 BOOT.md 指令中基于 datetime.now().weekday() 进行判断("如果是周一,还需检查每周部署日志")。指令是自由格式文本,agent 能推理的内容都可以使用。
  • 多个检查清单: 将 hook 指向不同文件(STARTUP.md、MORNING.md 等),并为每个文件注册独立的 hook 目录。
  • 非 agent 变体: 如果不需要完整的 agent 循环,完全跳过 AIAgent,直接通过 httpx 在处理器中发送固定通知。更轻量、更快速,且无 provider 依赖。

为什么这不是内置功能​

Hermes 早期版本将此作为内置 hook 发布,每次 gateway 启动时都会静默生成一个使用裸默认值的 agent。这让使用自定义端点的用户感到意外,也让不知道它在运行的用户无从察觉。将其作为文档化模式保留——由你在 hooks 目录中构建——意味着你能清楚地看到它的行为,并通过编写文件来选择启用。

工作原理​

  • Gateway 启动时,HookRegistry.discover_and_load() 扫描 ~/.hermes/hooks/
  • 每个包含 HOOK.yaml + handler.py 的子目录都会被动态加载
  • 处理器按其声明的事件注册
  • 在每个生命周期节点,hooks.emit() 触发所有匹配的处理器
  • 任何处理器中的错误都会被捕获并记录——损坏的 hook 永远不会导致 agent 崩溃

信息

Gateway hooks 仅在 gateway(Telegram、Discord、Slack、WhatsApp、Teams)中触发。CLI 不加载 gateway hooks。如需在所有环境中生效的 hook,请使用 plugin hooks。

Plugin Hooks​

插件可以注册在 CLI 和 gateway 会话中均会触发的 hook。这些 hook 通过插件 register() 函数中的 ctx.register_hook() 以编程方式注册。

def register(ctx):
    ctx.register_hook("pre_tool_call", my_tool_observer)
    ctx.register_hook("post_tool_call", my_tool_logger)
    ctx.register_hook("pre_llm_call", my_memory_callback)
    ctx.register_hook("post_llm_call", my_sync_callback)
    ctx.register_hook("on_session_start", my_init_callback)
    ctx.register_hook("on_session_end", my_cleanup_callback)

所有 hook 的通用规则:

  • 回调接收关键字参数;为保持向前兼容,请始终接受 **kwargs。
  • 回调异常会被记录并跳过,后续回调仍会继续。
  • 下表分类仅描述当前行为:观察者忽略返回值,Transform 接受第一个有效字符串替换,指令/控制消费已说明的返回结构。Plugin middleware 是独立的 registry/surface,不属于另一类 hook。
  • turn_id、api_request_id、task_id、session_id、api_call_count 等关联字段因 hook 而异,可能不存在;应将这些 ID 视为 opaque 值。
  • 运行时事件名以 hermes_cli.plugins.VALID_HOOKS 为准。hermes hooks list 只列出已配置的 shell/outbound hook,并非可用事件目录;只有 hermes hooks test <event> 收到无效事件时才会打印有效集合。

已发布的 plugin-hook 目录​

下表列出每个 call site 实际传入的事件专属字段。为保持向后兼容,PluginManager 还会向每个 plugin-hook 回调加入 telemetry_schema_version="hermes.observer.v1"。这个旧版 envelope 标记并不表示所有 hook payload 共用同一套语义 schema;新的版本化 contract 应归属于具体事件或 capability family。

| | Hook | 类别 | 精确时机与返回行为 | 显式 payload 字段 | 隐私/敏感性

| pre_tool_call | 指令/控制 | 执行前一次;第一个有效 block 或 approve 指令生效。 | tool_name, args, task_id, session_id, tool_call_id, turn_id, api_request_id, middleware_trace | 原始参数可能含用户内容、路径、命令或 secret。

| post_tool_call | 观察者 | 阻断、错误或成功结果产生后;忽略返回值。 | tool_name, args, result, task_id, session_id, tool_call_id, turn_id, api_request_id, duration_ms, status, error_type, error_message, middleware_trace | 结果/错误文本可能含任意工具或用户内容及 secret。

| transform_tool_result | Transform | post_tool_call 后、写入会话前;第一个字符串替换结果。 | tool_name, args, result, task_id, session_id, tool_call_id, turn_id, api_request_id, duration_ms, status, error_type, error_message | 暴露完整的 model-bound 结果和参数。

| transform_terminal_output | Transform | 前台进程输出完成有界捕获后、最终 output limit 前;第一个字符串替换输出。 | command, output, returncode, task_id, env_type | 命令/输出可能含凭据。

| pre_llm_call | 指令/控制 | 每轮 loop 前一次;所有有效字符串或 {"context": ...} 会拼接并注入用户消息。 | session_id, task_id, turn_id, user_message, conversation_history, is_first_turn, model, platform, parent_session_id, sender_id | 完整用户消息和会话历史。

| post_llm_call | 观察者 | 成功且未中断的轮次 finalize 时;忽略返回值。 | session_id, task_id, turn_id, user_message, assistant_response, conversation_history, model, platform | 完整 prompt、response 和 history。

| transform_llm_output | Transform | post_llm_call 和最终交付前;第一个非空字符串替换 response。 | response_text, session_id, model, platform | 完整最终 assistant 文本。

| pre_verify | 指令/控制 | 有界的代码编辑 verify gate;第一个有效 continue/block-stop 指令让轮次继续。 | session_id, platform, model, coding, attempt, final_response, changed_paths | 草稿 response 和变更路径。

| pre_api_request | 观察者 | 每次 provider attempt 发请求前;忽略返回值。 | task_id, turn_id, api_request_id, session_id, user_message, conversation_history, platform, model, provider, base_url, api_mode, api_call_count, retry_count, request_messages, message_count, tool_count, approx_input_tokens, request_char_count, max_tokens, started_at, middleware_trace, request | 高敏感:兼容字段 user_message、conversation_history、request_messages 故意保留原始值;新 consumer 应优先用已清理的 request。

| post_api_request | 观察者 | Provider success 归一化后;忽略返回值。 | task_id, turn_id, api_request_id, session_id, platform, model, provider, base_url, api_mode, api_call_count, api_duration, started_at, ended_at, finish_reason, message_count, response_model, response, usage, assistant_message, assistant_content_chars, assistant_tool_call_count | 可用已清理的 response,但原始归一化 assistant_message 可能含模型/用户内容;usage 是计费数据。

| api_request_error | 观察者 | 每次失败的 provider attempt;忽略返回值。 | task_id, turn_id, api_request_id, session_id, platform, model, provider, base_url, api_mode, api_call_count, api_duration, started_at, ended_at, status_code, retry_count, max_retries, retryable, reason, error, request | Error 文本可能含 provider/用户数据;request 设计为已清理。

| on_session_start | 观察者 | 新 session 第一轮;忽略返回值。 | session_id, model, platform | 仅标识符和 routing metadata。

| on_session_end | 观察者 | Canonical 路径在每轮 finalize;CLI/TUI 退出还有精简 legacy shape。 | Canonical:session_id, task_id, turn_id, completed, failed, interrupted, turn_exit_reason, model, platform;退出路径可能增加 reason/api_request_id 并省略字段。 | ID、model/platform 和结果;canonical payload 无消息正文。

| on_session_finalize | 观察者 | CLI/TUI/gateway 通过 finalize_session teardown;gateway 关闭时可只 finalize 而不 reset。忽略返回值。 | 按 surface:session_id, platform,可选 reason, old_session_id, new_session_id | Session 和 routing 标识。

| on_session_reset | 观察者 | CLI/TUI session boundary,或 gateway 创建替代 session 后;忽略返回值。 | CLI:session_id, platform, reason;TUI:session_id, platform;gateway:另有 reason, old_session_id, new_session_id | Session 和 routing 标识。

| on_session_delete | 观察者 | 会话删除或主动归档提交后(CLI、TUI、Desktop/dashboard、gateway API、sweep、prune);忽略返回值。 | session_id、reason、profile,可选 deleted_ids(多于一行时为全集)、sessions_dir | 仅 session 标识;无消息内容。

| on_skill_lifecycle | 观察者 | 权威 skill 使用状态变更后;忽略返回值。 | action, skill_name, provenance, task_id, session_id, use_count, reused, reuse_after_patch | 暴露本地 skill 名和 provenance。

| subagent_start | 观察者 | 子 agent 已构造、即将运行;忽略返回值。 | parent_session_id, parent_turn_id, parent_subagent_id, child_session_id, child_subagent_id, child_role, child_goal | Child goal 可能含用户/项目内容。

| subagent_stop | 观察者 | 子 agent 退出;忽略返回值。 | parent_session_id, parent_turn_id, child_session_id, child_role, child_summary, child_status, tool_call_history, duration_ms | Summary 和已脱敏 tool-history metadata 仍可能暴露项目结构。

| pre_gateway_dispatch | 指令/控制 | 非 internal 入站消息在 auth/pairing/dispatch 前;第一个有效 skip、rewrite 或 allow 控制流程。 | event, gateway, session_store | 极高权限的进程内对象会暴露入站用户/routing 数据和 host handle。

| pre_approval_request | 观察者 | Prompted 或 smart approval 前;忽略返回值。 | command, description, pattern_key, pattern_keys, session_key, surface, turn_id, tool_call_id | 命令可能含 secret;smart observer preparation 会强制脱敏,但各 surface 并非完全相同。

| post_approval_response | 观察者 | 决策、timeout 或 gateway 通知失败后;忽略返回值。 | command, description, pattern_key, pattern_keys, session_key, surface, turn_id, tool_call_id, choice;smart 路径可增加 decided_by | 同样的命令敏感性,加决策 metadata。

| kanban_task_claimed | 观察者 | Claim commit 后,在 dispatcher 进程 spawn worker 前;忽略返回值。 | task_id, profile_name, board, assignee, run_id | Board/task/profile/assignee 标识。

| kanban_task_completed | 观察者 | Completion 和 cleanup 后,通常在 worker 进程;忽略返回值。 | task_id, profile_name, board, assignee, run_id, summary | Summary 可能含项目/用户内容。

| kanban_task_blocked | 观察者 | Blocked transition 后;dependency-wait 路径在 transaction 退出前触发。忽略返回值。 | task_id, profile_name, board, assignee, run_id, reason | Reason 可能含项目/用户内容。

pre_tool_call​

在每次工具执行之前立即触发——内置工具和插件工具均适用。

回调签名:

def my_callback(tool_name: str, args: dict, task_id: str, **kwargs):

| | 参数 | 类型 | 描述

| tool_name | str | 即将执行的工具名称(如 "terminal"、"web_search"、"read_file")

| args | dict | 模型传递给工具的参数

| task_id | str | 会话/任务标识符。未设置时为空字符串。

触发位置: model_tools.py 中的 handle_function_call() 内,工具处理器运行前。每次工具调用触发一次——若模型并行调用 3 个工具,则触发 3 次。

返回值——阻断或要求审批:

return {"action": "block", "message": "Reason the tool call was blocked"}
# 或
return {"action": "approve", "message": "Why approval is required", "rule_key": "optional:scope"}

第一个有效指令生效。block 要求非空 message,并用该文本作为返回给模型的错误来短路工具。approve 将调用升级到现有的人类审批 gate;message 和 rule_key 可选,拒绝、timeout 或 gate error 都会 fail closed。其他返回值会被忽略。

使用场景: 日志记录、审计追踪、工具调用计数、阻断危险操作、速率限制、按用户策略执行。

示例——工具调用审计日志:

import json, logging
from datetime import datetime

logger = logging.getLogger(__name__)

def audit_tool_call(tool_name, args, task_id, **kwargs):
    logger.info("TOOL_CALL session=%s tool=%s args=%s",
                task_id, tool_name, json.dumps(args)[:200])

def register(ctx):
    ctx.register_hook("pre_tool_call", audit_tool_call)

示例——对危险工具发出警告:

DANGEROUS = {"terminal", "write_file", "patch"}

def warn_dangerous(tool_name, **kwargs):
    if tool_name in DANGEROUS:
        print(f"⚠ Executing potentially dangerous tool: {tool_name}")

def register(ctx):
    ctx.register_hook("pre_tool_call", warn_dangerous)

post_tool_call​

在每次工具执行返回之后立即触发。

回调签名:

def my_callback(tool_name: str, args: dict, result: str, task_id: str,
                duration_ms: int, **kwargs):

| | 参数 | 类型 | 描述

| tool_name | str | 刚刚执行的工具名称

| args | dict | 模型传递给工具的参数

| result | str | 工具的返回值(始终为 JSON 字符串)

| task_id | str | 会话/任务标识符。未设置时为空字符串。

| duration_ms | int | 工具分发耗时,单位毫秒(使用 time.monotonic() 在 registry.dispatch() 前后测量)。

触发位置: model_tools.py 中的 handle_function_call() 内,工具处理器返回后。每次工具调用触发一次。若工具抛出未处理异常,不会触发(错误被捕获并以错误 JSON 字符串返回,post_tool_call 以该错误字符串作为 result 触发)。

返回值: 忽略。

使用场景: 记录工具结果、指标采集、追踪工具成功/失败率、延迟仪表盘、按工具预算告警、特定工具完成时发送通知。

示例——追踪工具使用指标:

from collections import Counter, defaultdict
import json

_tool_counts = Counter()
_error_counts = Counter()
_latency_ms = defaultdict(list)

def track_metrics(tool_name, result, duration_ms=0, **kwargs):
    _tool_counts[tool_name] += 1
    _latency_ms[tool_name].append(duration_ms)
    try:
        parsed = json.loads(result)
        if "error" in parsed:
            _error_counts[tool_name] += 1
    except (json.JSONDecodeError, TypeError):
        pass

def register(ctx):
    ctx.register_hook("post_tool_call", track_metrics)

pre_llm_call​

每轮触发一次,在工具调用循环开始前。所有有效回调返回值会按插件顺序聚合,并注入当前轮次的用户消息。

回调签名:

def my_callback(session_id: str, user_message: str, conversation_history: list,
                is_first_turn: bool, model: str, platform: str, **kwargs):

| | 参数 | 类型 | 描述

| session_id | str | 当前会话的唯一标识符

| user_message | str | 本轮用户的原始消息(技能注入前)

| conversation_history | list | 完整消息列表的副本(OpenAI 格式:[{"role": "user", "content": "..."}])

| is_first_turn | bool | 新会话的第一轮为 True,后续轮次为 False

| model | str | 模型标识符(如 "anthropic/claude-sonnet-4.6")

| platform | str | 会话运行环境:"cli"、"telegram"、"discord" 等

触发位置: run_agent.py 中的 run_conversation() 内,上下文压缩后、主 while 循环前。每次 run_conversation() 调用触发一次(即每个用户轮次一次),而非工具循环内每次 API 调用触发一次。

返回值: 若回调返回包含 "context" 键的字典,或非空的普通字符串,该文本会追加到当前轮次的用户消息。返回 None 表示不注入。

# 注入上下文
return {"context": "Recalled memories:\n- User likes Python\n- Working on hermes-agent"}

# 普通字符串(等效)
return "Recalled memories:\n- User likes Python"

# 不注入
return None

上下文注入位置: 始终注入到用户消息,而非系统 prompt。这保留了 prompt 缓存——系统 prompt 在各轮次间保持不变,已缓存的 token 得以复用。系统 prompt 是 Hermes 的领域(模型指导、工具执行、个性、技能)。插件在用户输入旁边贡献上下文。

干净的用户消息 content 保持不变。为保证 replay 和 prompt cache 稳定,Hermes 可能把实际发送给 API 的消息(包括插件注入上下文)持久化到该行的 api_content sidecar。

当多个插件返回上下文时,其输出按插件发现顺序(按目录名字母顺序)以双换行符连接。

使用场景: 记忆召回、RAG 上下文注入、护栏、每轮分析。

示例——记忆召回:

import httpx

MEMORY_API = "https://your-memory-api.example.com"

def recall(session_id, user_message, is_first_turn, **kwargs):
    try:
        resp = httpx.post(f"{MEMORY_API}/recall", json={
            "session_id": session_id,
            "query": user_message,
        }, timeout=3)
        memories = resp.json().get("results", [])
        if not memories:
            return None
        text = "Recalled context:\n" + "\n".join(f"- {m['text']}" for m in memories)
        return {"context": text}
    except Exception:
        return None

def register(ctx):
    ctx.register_hook("pre_llm_call", recall)

示例——护栏:

POLICY = "Never execute commands that delete files without explicit user confirmation."

def guardrails(**kwargs):
    return {"context": POLICY}

def register(ctx):
    ctx.register_hook("pre_llm_call", guardrails)

post_llm_call​

每轮触发一次,在工具调用循环完成且 agent 产生最终响应后。仅在成功的轮次触发——若轮次被中断则不触发。

回调签名:

def my_callback(session_id: str, user_message: str, assistant_response: str,
                conversation_history: list, model: str, platform: str, **kwargs):

| | 参数 | 类型 | 描述

| session_id | str | 当前会话的唯一标识符

| user_message | str | 本轮用户的原始消息

| assistant_response | str | Agent 本轮的最终文本响应

| conversation_history | list | 轮次完成后完整消息列表的副本

| model | str | 模型标识符

| platform | str | 会话运行环境

触发位置: run_agent.py 中的 run_conversation() 内,工具循环以最终响应退出后。受 if final_response and not interrupted 保护——因此当用户在轮次中途中断,或 agent 在未产生响应的情况下达到迭代上限时,不会触发。

返回值: 忽略。

使用场景: 将对话数据同步到外部记忆系统、计算响应质量指标、记录轮次摘要、触发后续操作。

示例——同步到外部记忆:

import httpx

MEMORY_API = "https://your-memory-api.example.com"

def sync_memory(session_id, user_message, assistant_response, **kwargs):
    try:
        httpx.post(f"{MEMORY_API}/store", json={
            "session_id": session_id,
            "user": user_message,
            "assistant": assistant_response,
        }, timeout=5)
    except Exception:
        pass  # best-effort

def register(ctx):
    ctx.register_hook("post_llm_call", sync_memory)

示例——追踪响应长度:

import logging
logger = logging.getLogger(__name__)

def log_response_length(session_id, assistant_response, model, **kwargs):
    logger.info("RESPONSE session=%s model=%s chars=%d",
                session_id, model, len(assistant_response or ""))

def register(ctx):
    ctx.register_hook("post_llm_call", log_response_length)

on_session_start​

在全新会话创建时触发一次。在会话延续时不会触发(用户在已有会话中发送第二条消息时)。

回调签名:

def my_callback(session_id: str, model: str, platform: str, **kwargs):

| | 参数 | 类型 | 描述

| session_id | str | 新会话的唯一标识符

| model | str | 模型标识符

| platform | str | 会话运行环境

触发位置: run_agent.py 中的 run_conversation() 内,新会话第一轮期间——具体在系统 prompt 构建后、工具循环开始前。检查条件为 if not conversation_history(无历史消息 = 新会话)。

返回值: 忽略。

使用场景: 初始化会话级状态、预热缓存、向外部服务注册会话、记录会话开始。

示例——初始化会话缓存:

_session_caches = {}

def init_session(session_id, model, platform, **kwargs):
    _session_caches[session_id] = {
        "model": model,
        "platform": platform,
        "tool_calls": 0,
        "started": __import__("datetime").datetime.now().isoformat(),
    }

def register(ctx):
    ctx.register_hook("on_session_start", init_session)

on_session_end​

在每次 run_conversation() 调用结束时触发,无论结果如何。若用户在 agent 处理过程中退出,也会从 CLI 的退出处理器触发。

回调签名:

def my_callback(session_id: str, completed: bool, interrupted: bool,
                model: str, platform: str, **kwargs):

| | 参数 | 类型 | 描述

| session_id | str | 会话的唯一标识符

| completed | bool | Agent 产生最终响应时为 True,否则为 False

| interrupted | bool | 轮次被中断时为 True(用户发送新消息、/stop 或退出)

| model | str | 模型标识符

| platform | str | 会话运行环境

触发位置: 两处:

  • run_agent.py — 每次 run_conversation() 调用结束时,所有清理完成后。始终触发,即使轮次出错。
  • cli.py — CLI 的 atexit 处理器中,但仅当 agent 在退出时处于处理中状态(_agent_running=True)。这捕获了处理过程中的 Ctrl+C 和 /exit。此时 completed=False,interrupted=True。

返回值: 忽略。

使用场景: 刷新缓冲区、关闭连接、持久化会话状态、记录会话时长、清理 on_session_start 中初始化的资源。

示例——刷新并清理:

_session_caches = {}

def cleanup_session(session_id, completed, interrupted, **kwargs):
    cache = _session_caches.pop(session_id, None)
    if cache:
        # Flush accumulated data to disk or external service
        status = "completed" if completed else ("interrupted" if interrupted else "failed")
        print(f"Session {session_id} ended: {status}, {cache['tool_calls']} tool calls")

def register(ctx):
    ctx.register_hook("on_session_end", cleanup_session)

示例——会话时长追踪:

import time, logging
logger = logging.getLogger(__name__)

_start_times = {}

def on_start(session_id, **kwargs):
    _start_times[session_id] = time.time()

def on_end(session_id, completed, interrupted, **kwargs):
    start = _start_times.pop(session_id, None)
    if start:
        duration = time.time() - start
        logger.info("SESSION_DURATION session=%s seconds=%.1f completed=%s interrupted=%s",
                     session_id, duration, completed, interrupted)

def register(ctx):
    ctx.register_hook("on_session_start", on_start)
    ctx.register_hook("on_session_end", on_end)

on_session_finalize​

当 CLI 或 gateway 销毁活跃会话时触发——例如用户执行 /new,或 CLI 在 agent 活跃时退出。仅回收资源的空闲缓存淘汰不会结束持久化对话。可用它刷新与旧 session ID 绑定的状态。Gateway reset 时,替代会话会先创建并持久化,然后才调用此回调。

回调签名:

def my_callback(session_id: str | None, platform: str, **kwargs):

| | 参数 | 类型 | 描述

| session_id | str 或 None | 即将销毁的会话 ID。若无活跃会话则可能为 None。

| platform | str | "cli" 或消息平台名称("telegram"、"discord" 等)。

触发位置: CLI/TUI teardown,以及 gateway reset 或关闭路径。Gateway 关闭可只触发 finalize,而不触发对应的 on_session_reset。

返回值: 忽略。

使用场景: 在会话 ID 被丢弃前持久化最终会话指标、关闭每会话资源、发出最终遥测事件、排空队列写入。

on_session_reset​

在 CLI 或 TUI session boundary,或 gateway 为活跃聊天换入新会话 key 时触发。这让插件无需等待下一个 on_session_start 即可响应会话状态已被清除。

回调签名:

def my_callback(session_id: str, platform: str, **kwargs):

| | 参数 | 类型 | 描述

| session_id | str | 新会话的 ID(已轮换为新值)。

| platform | str | "cli"、"tui" 或消息平台名称。

| reason | str,可选 | CLI 和 gateway reset 路径提供。

| old_session_id | str,可选 | 仅 gateway,旧 session ID。

| new_session_id | str,可选 | 仅 gateway,新 session ID。

触发位置: CLI 提供 session_id、platform、reason;TUI 提供 session_id、platform;gateway 在分配新 key 后另加 reason、old_session_id、new_session_id。Gateway reset 顺序为:创建并持久化替代会话 → on_session_finalize(old_id) → on_session_reset(new_id) → 第一条入站消息时的 on_session_start(new_id)。

返回值: 忽略。

使用场景: 重置以 session_id 为键的每会话缓存、发出"会话已轮换"分析事件、初始化新状态桶。

参见 构建插件指南,获取包含工具 schema、处理器和高级 hook 模式的完整演练。

subagent_stop​

delegate_task 完成后,每个子 agent 触发一次。无论你委托了单个任务还是三个任务的批次,此 hook 对每个子 agent 各触发一次,在父线程上串行执行。

回调签名:

def my_callback(parent_session_id: str, child_role: str | None,
                child_summary: str | None, child_status: str,
                duration_ms: int, **kwargs):

| | 参数 | 类型 | 描述

| parent_session_id | str | 委托父 agent 的会话 ID

| child_role | str | None | 子 agent 上设置的编排角色标签(若功能未启用则为 None)

| child_summary | str | None | 子 agent 返回给父 agent 的最终响应

| child_status | str | "completed"、"failed"、"interrupted" 或 "error"

| duration_ms | int | 运行子 agent 的挂钟时间,单位毫秒

触发位置: tools/delegate_tool.py 中,ThreadPoolExecutor.as_completed() 排空所有子 future 后。触发被编排到父线程,因此 hook 作者无需考虑并发回调执行问题。

返回值: 忽略。

使用场景: 记录编排活动、为计费累计子 agent 时长、写入委托后审计记录。

示例——记录编排器活动:

import logging
logger = logging.getLogger(__name__)

def log_subagent(parent_session_id, child_role, child_status, duration_ms, **kwargs):
    logger.info(
        "SUBAGENT parent=%s role=%s status=%s duration_ms=%d",
        parent_session_id, child_role, child_status, duration_ms,
    )

def register(ctx):
    ctx.register_hook("subagent_stop", log_subagent)

信息

在大量委托场景下(如编排器角色 × 5 个叶节点 × 嵌套深度),subagent_stop 每轮会触发多次。保持回调快速执行;将耗时操作推送到后台队列。

pre_gateway_dispatch​

在 gateway 中,每条入站 MessageEvent 触发一次,在内部事件守卫之后、认证/配对和 agent 分发之前。这是 gateway 级消息流策略(只听不回窗口、人工接管、按聊天路由等)的拦截点,这些策略不适合放在任何单一平台适配器中。

回调签名:

def my_callback(event, gateway, session_store, **kwargs):

| | 参数 | 类型 | 描述

| event | MessageEvent | 标准化的入站消息(包含 .text、.source、.message_id、.internal 等)。

| gateway | GatewayRunner | 活跃的 gateway 运行器,插件可调用 gateway.adapters[platform].send(...) 进行旁路回复(所有者通知等)。

| session_store | SessionStore | 用于通过 session_store.append_to_transcript(...) 静默摄入转录。

触发位置: gateway/run.py 中的 GatewayRunner._handle_message() 内,is_internal 计算后立即触发。内部事件完全跳过此 hook(它们是系统生成的——后台进程完成等——不得被面向用户的策略拦截)。

返回值: None 或字典。第一个被识别的 action 字典生效;其余插件结果被忽略。插件回调中的异常会被捕获并记录;gateway 在出错时始终回退到正常分发。

| | 返回值 | 效果

| {"action": "skip", "reason": "..."} | 丢弃消息——无 agent 回复、无配对流程、无认证。假定插件已处理(如静默摄入到转录)。

| {"action": "rewrite", "text": "new text"} | 替换 event.text,然后以修改后的事件继续正常分发。适用于将缓冲的环境消息合并为单个 prompt。

| {"action": "allow"} / None | 正常分发——运行完整的认证/配对/agent 循环链。

使用场景: 只听不回的群聊(仅在被 @ 时响应;将环境消息缓冲为上下文);人工接管(所有者手动处理聊天时静默摄入客户消息);按 profile 速率限制;策略驱动的路由。

示例——静默丢弃未授权的私信,不触发配对代码:

def deny_unauthorized_dms(event, **kwargs):
    src = event.source
    if src.chat_type == "dm" and not _is_approved_user(src.user_id):
        return {"action": "skip", "reason": "unauthorized-dm"}
    return None

def register(ctx):
    ctx.register_hook("pre_gateway_dispatch", deny_unauthorized_dms)

示例——在被提及时将环境消息缓冲重写为单个 prompt:

_buffers = {}

def buffer_or_rewrite(event, **kwargs):
    key = (event.source.platform, event.source.chat_id)
    buf = _buffers.setdefault(key, [])
    if _bot_mentioned(event.text):
        combined = "\n".join(buf + [event.text])
        buf.clear()
        return {"action": "rewrite", "text": combined}
    buf.append(event.text)
    return {"action": "skip", "reason": "ambient-buffered"}

def register(ctx):
    ctx.register_hook("pre_gateway_dispatch", buffer_or_rewrite)

pre_approval_request​

在审批请求向用户展示之前立即触发——覆盖所有界面:交互式 CLI、Ink TUI、gateway 平台(Telegram、Discord、Slack、WhatsApp、Matrix 等)以及 ACP 客户端(VS Code、Zed、JetBrains)。

这是接入自定义通知器的正确位置——例如弹出允许/拒绝通知的 macOS 菜单栏应用,或记录每个带上下文审批请求的审计日志。

回调签名:

def my_callback(
    command: str,
    description: str,
    pattern_key: str,
    pattern_keys: list[str],
    session_key: str,
    surface: str,
    **kwargs,
):

| | 参数 | 类型 | 描述

| command | str | 等待审批的 shell 命令

| description | str | 命令被标记的人类可读原因(多个模式匹配时合并)

| pattern_key | str | 触发审批的主要模式键(如 "rm_rf"、"sudo")

| pattern_keys | list[str] | 所有匹配的模式键

| session_key | str | 会话标识符,用于按聊天限定通知范围

| surface | str | 交互式 CLI/TUI 提示为 "cli",异步平台审批为 "gateway"

返回值: 忽略。此处的 hook 仅作观察用途;不能否决或预先回答审批。使用 pre_tool_call 在工具到达审批系统前阻断它。

使用场景: 桌面通知、推送告警、审计日志、Slack webhook、升级路由、指标。

示例——macOS 桌面通知:

import subprocess

def notify_approval(command, description, session_key, **kwargs):
    title = "Hermes needs approval"
    body = f"{description}: {command[:80]}"
    subprocess.Popen([
        "osascript", "-e",
        f'display notification "{body}" with title "{title}"',
    ])

def register(ctx):
    ctx.register_hook("pre_approval_request", notify_approval)

post_approval_response​

在用户响应审批提示、提示超时,或 gateway 无法发送审批通知之后触发。通知失败会在尚无审批决定时以 choice="notify_failed" 触发。

回调签名:

def my_callback(
    command: str,
    description: str,
    pattern_key: str,
    pattern_keys: list[str],
    session_key: str,
    surface: str,
    choice: str,
    **kwargs,
):

与 pre_approval_request 相同的 kwargs,另加:

| | 参数 | 类型 | 描述

| choice | str | Prompted 路径使用 "once"、"session"、"always"、"deny"、"timeout" 或 "notify_failed";smart 路径使用 "smart_approve" 或 "smart_deny"

| decided_by | str | Smart 决策为 "aux_llm";prompted 路径不存在。

返回值: 忽略。

使用场景: 关闭对应的桌面通知、在审计日志中记录最终决定、更新指标、推进速率限制器。

def log_decision(command, choice, session_key, **kwargs):
    logger.info("approval %s: %s for session %s", choice, command[:60], session_key)

def register(ctx):
    ctx.register_hook("post_approval_response", log_decision)

transform_tool_result​

在工具返回之后、结果追加到对话之前触发。允许插件重写任意工具的结果字符串——不仅限于终端输出——在模型看到之前进行处理。

回调签名:

def my_callback(tool_name: str, args: dict, result: str, task_id: str, **kwargs) -> str | None:

完整 payload 还包括 session_id、tool_call_id、turn_id、api_request_id、duration_ms、status、error_type、error_message。result 是 tool dispatch 返回的最终结果;它和 args 都可能包含任意用户/工具内容及 secret。

返回值: 第一个 str(包括空字符串)替换结果,None 保持不变。

使用场景: 从 web_extract 输出中脱敏组织特定的 PII、为长 JSON 工具响应添加摘要头、向 read_file 结果注入检索增强提示、将 delegate_task 子 agent 报告重写为项目特定 schema。

import re
SECRET = re.compile(r"sk-[A-Za-z0-9]{32,}")

def redact_secrets(tool_name, result, **kwargs):
    if SECRET.search(result):
        return SECRET.sub("[REDACTED]", result)
    return None

def register(ctx):
    ctx.register_hook("transform_tool_result", redact_secrets)

适用于所有工具。仅针对 terminal 的重写请参见下方 transform_terminal_output——它范围更窄,在 transform_tool_result 前运行,且替换内容仍受 terminal 工具的最终 output limit 限制。

transform_terminal_output​

在 terminal 工具完成前台进程的有界输出捕获后、最终 output limit 前触发。插件可替换已捕获的 stdout/stderr;替换内容仍会受最终 output limit 限制。

回调签名:

def my_callback(
    command: str,
    output: str,
    returncode: int,
    task_id: str,
    env_type: str,
    **kwargs,
) -> str | None:

| | 参数 | 类型 | 描述

| command | str | 产生输出的 shell 命令。

| output | str | 有界进程捕获后的合并 stdout/stderr。

| returncode | int | 进程返回码。

| task_id | str | 有效 task ID,未设置时为空字符串。

| env_type | str | 执行环境类型。

返回值: 第一个 str 替换输出,None 保持不变。命令和输出可能包含凭据或其他敏感数据。

def summarize_find(command, output, **kwargs):
    if command.startswith("find ") and len(output) > 50_000:
        lines = output.count("\n")
        head = "\n".join(output.splitlines()[:40])
        return f"{head}\n\n[summary: {lines} paths total, showing first 40]"
    return None

def register(ctx):
    ctx.register_hook("transform_terminal_output", summarize_find)

与随后运行的 transform_tool_result 配合使用;后者覆盖所有工具,包括 terminal。

transform_llm_output​

每轮触发一次,在工具调用循环完成且模型产生最终响应后、该响应交付给用户(CLI、gateway 或程序调用方)之前。允许插件使用经典编程方法重写 assistant 的最终文本——无需为 SOUL 风格文本或技能驱动的转换消耗额外推理 token。

回调签名:

def my_callback(
    response_text: str,
    session_id: str,
    model: str,
    platform: str,
    **kwargs,
) -> str | None:

| | 参数 | 类型 | 描述

| response_text | str | 本轮 assistant 的最终响应文本。

| session_id | str | 本次对话的会话 ID(一次性运行时可能为空)。

| model | str | 产生响应的模型名称(如 anthropic/claude-sonnet-4.6)。

| platform | str | 交付平台(cli、telegram、discord 等;未设置时为空)。

返回值: 非空 str 替换响应文本,None 或空字符串保持不变。当多个插件注册时,第一个非空字符串生效。与 tool/terminal transform 不同,空字符串不会作为替换值。

使用场景: 应用个性/词汇转换(海盗腔、海绵宝宝体)、从最终文本中脱敏用户特定标识符、追加项目特定签名页脚、在不消耗 SOUL 指令 token 的情况下执行内部风格指南。

import os, re

def spongebob(response_text, **kwargs):
    if os.environ.get("SPONGEBOB_MODE") != "on":
        return None  # pass through unchanged
    return re.sub(r"!", "!! Tartar sauce!", response_text)

def register(ctx):
    ctx.register_hook("transform_llm_output", spongebob)

此 hook 受非空、非中断响应保护——不会在停止按钮中断或空轮次时触发。异常会被记录为警告,不会中断 agent 执行。

API request 观察者 hook​

pre_api_request​

每次 provider attempt 发出前触发,仅观察。兼容字段 user_message、conversation_history、request_messages 是原始且故意未清理的数据;新 consumer 应优先使用已清理的 request envelope。

post_api_request​

Provider response 成功归一化后触发,仅观察。优先使用已清理的 response;assistant_message 是原始归一化消息,usage 是计费数据。

api_request_error​

Provider attempt 失败时触发,包含 status/retry timing、error 对象和已清理的 request,仅观察。Error message 仍可能包含 provider 或用户数据。

on_skill_lifecycle​

权威 skill 使用状态变更后触发,仅观察;会暴露本地 skill_name、provenance、关联 ID、use count 和 reuse flag。

Kanban 生命周期观察者​

kanban_task_claimed​

Claim commit 后,在 dispatcher 进程 spawn worker 前触发。

kanban_task_completed​

Completion 和 cleanup 后触发,通常位于 worker 进程;summary 可能包含项目或用户内容。

kanban_task_blocked​

普通 blocked transition 后触发;dependency-wait 路径在该 write transaction 退出前调用。reason 可能包含项目或用户内容。

三个 kanban hook 均仅观察,并携带 task_id、profile_name、board、assignee、run_id;completed 增加 summary,blocked 增加 reason。

Shell Hooks​

在 cli-config.yaml 中声明 shell 脚本 hook,Hermes 会在对应的插件 hook 事件触发时将其作为子进程运行——在 CLI 和 gateway 会话中均适用。无需编写 Python 插件。

当你希望用一个即插即用的单文件脚本(Bash、Python 或任何带 shebang 的脚本)来实现以下功能时,使用 shell hooks:

  • 阻断工具调用 — 拒绝危险的 terminal 命令、执行按目录策略、要求对破坏性的 write_file / patch 操作进行审批。
  • 工具调用后运行 — 自动格式化 agent 刚写入的 Python 或 TypeScript 文件、记录 API 调用、触发 CI 工作流。
  • 向下一个 LLM 轮次注入上下文 — 在用户消息前追加 git status 输出、当前星期几或检索到的文档(参见 pre_llm_call)。
  • 观察生命周期事件 — 在子 agent 完成(subagent_stop)或会话开始(on_session_start)时写入日志行。

Shell hooks 通过在 CLI 启动(hermes_cli/main.py)和 gateway 启动(gateway/run.py)时调用 agent.shell_hooks.register_from_config(cfg) 来注册。它们与 Python 插件 hook 自然组合——两者都流经同一个分发器。

对比一览​

| | 维度 | Shell hooks | Plugin hooks | Gateway hooks

| 声明位置 | ~/.hermes/config.yaml 中的 hooks: 块 | 插件 plugin.yaml 中的 register() | HOOK.yaml + handler.py 目录

| 存放位置 | ~/.hermes/agent-hooks/(约定) | ~/.hermes/plugins/<name>/ | ~/.hermes/hooks/<name>/

| 语言 | 任意(Bash、Python、Go 二进制等) | 仅 Python | 仅 Python

| 运行环境 | CLI + Gateway | CLI + Gateway | 仅 Gateway

| 事件 | VALID_HOOKS(含 subagent_stop) | VALID_HOOKS | Gateway 生命周期(gateway:startup、agent:*、command:*)

| 可阻断工具调用 | 是(pre_tool_call) | 是(pre_tool_call) | 否

| 可注入 LLM 上下文 | 是(pre_llm_call) | 是(pre_llm_call) | 否

| 授权 | 每个 (event, command) 对首次使用时提示 | 隐式(Python 插件信任) | 隐式(目录信任)

| 进程间隔离 | 是(子进程) | 否(进程内) | 否(进程内)

配置 schema​

hooks:
  <event_name>:                  # Must be in VALID_HOOKS
    - matcher: "<regex>"         # Optional; used for pre/post_tool_call only
      command: "<shell command>" # Required; runs via shlex.split, shell=False
      timeout: <seconds>         # Optional; default 60, capped at 300

hooks_auto_accept: false         # See "Consent model" below

事件名称必须是 plugin hook 事件之一;拼写错误会产生"你是否想输入 X?"警告并被跳过。单个条目中的未知键会被忽略;缺少 command 会跳过并发出警告。timeout > 300 会被截断并发出警告。

JSON 通信协议​

每次事件触发时,Hermes 为每个匹配的 hook(在 matcher 允许的情况下)生成一个子进程,将 JSON 载荷通过 stdin 传入,并从 stdout 读取 JSON 响应。

stdin——脚本接收的载荷:

{
  "hook_event_name": "pre_tool_call",
  "tool_name":       "terminal",
  "tool_input":      {"command": "rm -rf /"},
  "session_id":      "sess_abc123",
  "cwd":             "/home/user/project",
  "extra":           {"task_id": "...", "tool_call_id": "..."}
}

对于非工具事件(pre_llm_call、subagent_stop、会话生命周期),tool_name 和 tool_input 为 null。extra 字典携带所有事件特定的 kwargs(user_message、conversation_history、child_role、duration_ms 等)。不可序列化的值会被字符串化而非省略。

stdout——可选响应:

// Block a pre_tool_call (both shapes accepted; normalised internally):
{"decision": "block", "reason":  "Forbidden: rm -rf"}   // Claude-Code style
{"action":   "block", "message": "Forbidden: rm -rf"}   // Hermes-canonical

// Inject context for pre_llm_call:
{"context": "Today is Friday, 2026-04-17"}

// Silent no-op — any empty / non-matching output is fine:

格式错误的 JSON、非零退出码和超时会记录警告,但永远不会中止 agent 循环。

实际示例​

1. 每次写入后自动格式化 Python 文件​

# ~/.hermes/config.yaml
hooks:
  post_tool_call:
    - matcher: "write_file|patch"
      command: "~/.hermes/agent-hooks/auto-format.sh"
#!/usr/bin/env bash
# ~/.hermes/agent-hooks/auto-format.sh
payload="$(cat -)"
path=$(echo "$payload" | jq -r '.tool_input.path // empty')
[[ "$path" == *.py ]] && command -v black >/dev/null && black "$path" 2>/dev/null
printf '{}\n'

Agent 的上下文内文件视图不会自动重新读取——重新格式化仅影响磁盘上的文件。后续的 read_file 调用会读取格式化后的版本。

2. 阻断破坏性 terminal 命令​

hooks:
  pre_tool_call:
    - matcher: "terminal"
      command: "~/.hermes/agent-hooks/block-rm-rf.sh"
      timeout: 5
#!/usr/bin/env bash
# ~/.hermes/agent-hooks/block-rm-rf.sh
payload="$(cat -)"
cmd=$(echo "$payload" | jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -qE 'rm[[:space:]]+-rf?[[:space:]]+/'; then
  printf '{"decision": "block", "reason": "blocked: rm -rf / is not permitted"}\n'
else
  printf '{}\n'
fi

3. 向每轮注入 git status(Claude-Code UserPromptSubmit 等效)​

hooks:
  pre_llm_call:
    - command: "~/.hermes/agent-hooks/inject-cwd-context.sh"
#!/usr/bin/env bash
# ~/.hermes/agent-hooks/inject-cwd-context.sh
cat - >/dev/null   # discard stdin payload
if status=$(git status --porcelain 2>/dev/null) && [[ -n "$status" ]]; then
  jq --null-input --arg s "$status" \
     '{context: ("Uncommitted changes in cwd:\n" + $s)}'
else
  printf '{}\n'
fi

Claude Code 的 UserPromptSubmit 事件在 Hermes 中没有对应的独立事件——pre_llm_call 在相同位置触发,且已支持上下文注入。在此使用即可。

4. 记录每次子 agent 完成​

hooks:
  subagent_stop:
    - command: "~/.hermes/agent-hooks/log-orchestration.sh"
#!/usr/bin/env bash
# ~/.hermes/agent-hooks/log-orchestration.sh
log=~/.hermes/logs/orchestration.log
jq -c '{ts: now, parent: .session_id, extra: .extra}' < /dev/stdin >> "$log"
printf '{}\n'

授权模型​

每个唯一的 (event, command) 对在 Hermes 首次遇到时会提示用户审批,然后将决定持久化到 ~/.hermes/shell-hooks-allowlist.json。后续运行(CLI 或 gateway)跳过提示。

三种方式可绕过交互式提示——满足其一即可:

  • CLI 上的 --accept-hooks 标志(如 hermes --accept-hooks chat)
  • HERMES_ACCEPT_HOOKS=1 环境变量
  • cli-config.yaml 中的 hooks_auto_accept: true

非 TTY 运行(gateway、cron、CI)需要这三种方式之一——否则任何新添加的 hook 会静默保持未注册状态并记录警告。

脚本编辑被静默信任。 允许列表以精确的命令字符串为键,而非脚本的哈希值,因此编辑磁盘上的脚本不会使授权失效。hermes hooks doctor 会标记 mtime 漂移,以便你发现编辑并决定是否重新审批。

hermes hooks CLI​

| | 命令 | 功能

| hermes hooks list | 列出已配置的 hook,包含 matcher、超时和授权状态

| hermes hooks test <event> [--for-tool X] [--payload-file F] | 对合成载荷触发所有匹配的 hook 并打印解析后的响应

| hermes hooks revoke <command> | 删除所有匹配 <command> 的允许列表条目(下次重启后生效)

| hermes hooks doctor | 对每个已配置的 hook 检查:执行位、允许列表状态、mtime 漂移、JSON 输出有效性和大致执行时间

安全性​

Shell hooks 以你的完整用户凭据运行——与 cron 条目或 shell 别名的信任边界相同。将 config.yaml 中的 hooks: 块视为特权配置:

  • 只引用你自己编写或完整审查过的脚本。
  • 将脚本保存在 ~/.hermes/agent-hooks/ 内,便于审计路径。
  • 拉取共享配置后重新运行 hermes hooks doctor,在新添加的 hook 注册前发现它们。
  • 如果你的 config.yaml 在团队中进行版本控制,审查修改 hooks: 部分的 PR 时应与审查 CI 配置一样严格。

顺序与优先级​

Python 插件 hook 和 shell hook 都流经同一个 invoke_hook() 分发器。Python 插件先注册(discover_and_load()),shell hook 后注册(register_from_config()),因此在平局情况下 Python pre_tool_call 的 block 决定优先。第一个有效的 block 生效——聚合器在任何回调产生带非空 message 的 {"action": "block", "message": str} 时立即返回。

Outbound Webhooks​

在 config.yaml 的 hooks.outbound: 中为一个或多个已发布事件配置外部 HTTP endpoint。Outbound webhook 是仅通知的 consumer:回调会序列化事件并放入有界队列,由 daemon worker 发送,因此 endpoint 的返回值不能阻断、转换或引导 agent。可使用环境变量提供 HMAC secret;接收方应校验签名,并将 payload 视为与上表相同敏感度的数据。

hooks:
  outbound:
    - url: "https://example.com/hermes-events"
      events: [post_tool_call, on_session_end]
      secret_env: HERMES_WEBHOOK_SECRET

配置中的事件名同样以 hermes_cli.plugins.VALID_HOOKS 为准。hermes hooks list 会列出这些已配置 target,但不会输出完整的可用事件目录。

持久目标(/goal)

/goal 为 Hermes 设置一个跨轮次持续存在的目标。每轮结束后,一个轻量级裁判模型会检查目标是否已被助手的最新回复满足。若未满足,Hermes 会自动将一条续行 prompt(提示词)注入同一会话并继续工作——直到目标达成、你暂停或清除目标,或者轮次预算耗尽为止。

这是我们对 Ralph loop 的实现,直接受 Eric Traut(OpenAI)在 Codex CLI 0.128.0 的 /goal 中的启发。核心思路——跨轮次保持目标存活、不达成不停止——源自他们。此处的实现是独立的,并已适配 Hermes 的架构。

适用场景​

当你希望 Hermes 自主迭代、无需每轮重新提示时,使用 /goal:

  • "修复 src/ 中的所有 lint 错误,并验证 ruff check 通过"
  • "从仓库 Y 移植功能 X,包含测试,并让 CI 变绿"
  • "调查为何会话 ID 有时在中途压缩时发生漂移,并撰写报告"
  • "构建一个小型 CLI,按 EXIF 日期重命名文件,然后对 photos/ 文件夹进行测试"

只需一轮即可完成的任务不需要 /goal。否则你需要说三次"继续" 的任务,才是它的用武之地。

快速开始​

/goal Fix every failing test in tests/hermes_cli/ and make sure scripts/run_tests.sh passes for that directory

你将看到:

  • 目标已接受 — ⊙ Goal set (20-turn budget): <your goal>
  • 第 1 轮运行 — Hermes 开始工作,就像你发送了一条普通消息一样。
  • 裁判运行 — 轮次结束后,裁判模型判定 done 或 continue。
  • 若需要则触发循环 — 若为 continue,你将看到 ↻ Continuing toward goal (1/20): <judge's reason>,Hermes 自动执行下一步。
  • 终止 — 最终你会看到 ✓ Goal achieved: <reason> 或 ⏸ Goal paused — N/20 turns used。

命令​

| | 命令 | 功能

| /goal <text> | 设置(或替换)持续目标。立即启动第一轮,无需再发送单独消息。

| /goal 或 /goal status | 显示当前目标、状态及已用轮次。

| /goal pause | 停止自动续行循环,但不清除目标。

| /goal resume | 恢复循环(将轮次计数器重置为零)。

| /goal clear | 完全删除目标。

在 CLI 及所有 gateway 平台(Telegram、Discord、Slack、Matrix、Signal、WhatsApp、SMS、iMessage、Webhook、API server 以及 Web 控制台)上行为完全一致。

目标进行中追加条件:/subgoal​

目标激活期间,你可以使用 /subgoal <text> 追加额外的验收条件,而不会重置循环。每次调用会向目标的子目标列表添加一个编号条目;下一轮 agent 看到的续行 prompt 包含原始目标以及一个"用户在循环中途追加的额外条件"块,裁判 prompt 也会被重写,使裁判在判定时必须考虑所有子目标——只有原始目标和所有子目标均满足时,目标才会被标记为完成。

| | 命令 | 功能

| /subgoal <text> | 向活跃目标追加一个新条件。需要有活跃的 /goal。

| /subgoal(无参数) | 显示当前编号子目标列表。

| /subgoal remove <N> | 删除第 N 个子目标(从 1 开始计数)。

| /subgoal clear | 删除所有子目标,但保留原始目标。

子目标与目标一起持久化存储在 SessionDB.state_meta 中,因此在 /resume 后依然有效。设置新的 /goal <text> 会替换目标并清空子目标列表;/goal clear 同样如此。

当你启动一个循环("修复失败的测试")后,中途发现还需要"为刚修复的 bug 添加回归测试"时,使用此功能——/subgoal add a regression test 可在不中断运行循环的情况下收紧成功条件。

行为细节​

裁判​

每轮结束后,Hermes 会调用一个辅助模型,传入:

  • 持续目标文本
  • agent 最新的最终回复(最后约 4 KB 文本)
  • 一个系统 prompt,要求裁判以严格 JSON 格式回复:{"done": <bool>, "reason": "<one-sentence rationale>"}

裁判刻意保守:只有当回复明确确认目标已完成、最终交付物已清晰产出,或目标不可达/被阻塞时(视为 DONE 并附带阻塞原因,以免在不可能的任务上消耗预算),才会将目标标记为 done。

失败开放语义​

若裁判出错(网络抖动、响应格式错误、辅助客户端不可用),Hermes 将判定视为 continue——损坏的裁判不会阻塞进度。轮次预算才是真正的兜底机制。

轮次预算​

默认为 20 个续行轮次(config.yaml 中的 goals.max_turns)。预算耗尽时,Hermes 自动暂停并告知你如何继续:

⏸ Goal paused — 20/20 turns used. Use /goal resume to keep going, or /goal clear to stop.

/goal resume 将计数器重置为零,你可以按可控的块继续推进。

用户消息始终优先​

目标激活期间,你发送的任何真实消息都优先于续行循环。在 CLI 上,你的消息会在队列中的续行消息之前进入 _pending_input;在 gateway 上,它以同样的方式通过适配器 FIFO 传递。你的轮次结束后裁判会再次运行——因此如果你的消息恰好完成了目标,裁判会捕获到并停止循环。

运行中安全性(gateway)​

agent 正在运行时,/goal status、/goal pause 和 /goal clear 可以安全执行——它们只操作控制面状态,不会中断当前轮次。在运行中设置新目标(/goal <new text>)会被拒绝,并提示你先执行 /stop,以防旧续行与新目标产生竞争。

持久化​

目标状态存储在 SessionDB.state_meta 中,以 goal:<session_id> 为键。这意味着 /resume 可以从你离开的地方继续——设置目标、合上笔记本、明天回来、执行 /resume,目标依然完好如初(活跃、暂停或已完成)。

Prompt 缓存​

续行 prompt 是一条以用户角色追加到历史记录中的普通消息。它不会修改系统 prompt、切换工具集,也不会以任何使 Hermes prompt 缓存失效的方式改动对话。运行一个 20 轮目标,在缓存层面与 20 轮普通对话的开销相同。

配置​

在 ~/.hermes/config.yaml 中添加:

goals:
  # Hermes 自动暂停并要求你执行 /goal resume 之前的最大续行轮次。
  # 默认 20。若想要更紧凑的循环可降低此值;
  # 长时间重构可适当提高。
  max_turns: 20

选择裁判模型​

裁判使用 goal_judge 辅助任务。默认情况下,它解析为你的主模型(参见辅助模型)。若想将裁判路由到廉价快速的模型以降低成本,可添加覆盖配置:

auxiliary:
  goal_judge:
    provider: openrouter
    model: google/gemini-3-flash-preview

裁判调用量小(约 200 个输出 token),每轮运行一次,因此廉价快速的模型通常是正确选择。

示例演练​

You: /goal Create four files /tmp/note_{1..4}.txt, one per turn, each containing its number as text

  ⊙ Goal set (20-turn budget): Create four files /tmp/note_{1..4}.txt, one per turn, each containing its number as text

Hermes: Creating /tmp/note_1.txt now.
  💻 echo "1" > /tmp/note_1.txt   (0.1s)
  I've created /tmp/note_1.txt with the content "1". I'll continue with the remaining files on the next turn as you specified.

  ↻ Continuing toward goal (1/20): Only 1 of 4 files has been created; 3 files remain.

Hermes: [Continuing toward your standing goal]
  💻 echo "2" > /tmp/note_2.txt   (0.1s)
  Created /tmp/note_2.txt. Two more to go.

  ↻ Continuing toward goal (2/20): 2 of 4 files created; 2 remain.

Hermes: [Continuing toward your standing goal]
  💻 echo "3" > /tmp/note_3.txt   (0.1s)
  Created /tmp/note_3.txt.

  ↻ Continuing toward goal (3/20): 3 of 4 files created; 1 remains.

Hermes: [Continuing toward your standing goal]
  💻 echo "4" > /tmp/note_4.txt   (0.1s)
  All four files have been created: /tmp/note_1.txt through /tmp/note_4.txt, each containing its number.

  ✓ Goal achieved: All four files were created with the specified content, completing the goal.

You: _

四轮,一次 /goal 调用,你零次"继续"提示。

裁判判断有误时​

没有裁判是完美的。需注意两种失败模式:

假阴性——目标实际已完成,裁判却说继续。 轮次预算会兜底。你会看到 ⏸ Goal paused,可以执行 /goal clear 或直接发送新消息。

假阳性——工作尚未完成,裁判却说已完成。 你会看到 ✓ Goal achieved,但你知道实际情况并非如此。发送后续消息继续,或更精确地重新设置目标:/goal <更具体的文本>。裁判的系统 prompt 刻意保守,以使假阳性比假阴性更少出现。

如果你觉得某次裁判判定不可信,↻ Continuing toward goal 或 ✓ Goal achieved 行中的原因文本会告诉你裁判看到了什么。这通常足以诊断出是目标文本存在歧义,还是模型的回复有问题。

致谢​

/goal 是 Hermes 对 Ralph loop 模式的实现。面向用户的设计——跨轮次保持目标存活、不达成不停止,以及创建/暂停/恢复/清除控制——由 OpenAI Codex 团队的 Eric Traut 在 Codex CLI 0.128.0 中推广并落地。我们的实现是独立的(中央 CommandDef 注册表、SessionDB.state_meta 持久化、辅助客户端裁判、gateway 侧的适配器 FIFO 续行),但这个想法源自他们。功劳归于应得之人。