第十一篇:动手实验手册
概述
本手册包含 6 个渐进式实验(Lab),每个实验建立在前一个实验的基础之上,最终形成一套完整的 Hermes Agent 开发、集成、扩展与生产部署能力。所有实验均可在当前服务器上直接执行。
通用前置条件:
- Linux 服务器(Ubuntu 22.04+),已安装 Python 3.10+、git
- 拥有 sudo 权限(Lab 3、6 需要)
- 一个可用的 LLM API Key(Anthropic / OpenRouter / 其他兼容 provider)
Lab 1:安装配置 + CLI 基础
Lab 目标:完成 Hermes Agent 的源码安装、配置初始化,掌握 CLI 交互模式和常用命令。
预计时间:30 分钟
前置条件:服务器已安装 git、Python 3.10+、uv(或 pip)
步骤 1:克隆仓库并安装
# 克隆源码
cd /opt
sudo git clone <repo-url> hermes-agent
sudo chown -R $(whoami):$(whoami) /opt/hermes-agent
cd /opt/hermes-agent
# 使用 uv 安装(推荐)
uv pip install -e ".[all]"
# 或使用 pip 安装
# pip install -e ".[all]"
# 验证安装
hermes --version
也可以使用项目自带的安装脚本:
cd /opt/hermes-agent
./setup-hermes.sh
步骤 2:初始化配置
# 运行交互式配置向导
hermes setup
向导会引导你配置 model、provider、API Key。也可以手动创建配置文件:
# 创建配置目录
mkdir -p ~/.hermes
# 创建 .env 文件存放 API Key
cat > ~/.hermes/.env << 'EOF'
OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxx
# 或使用其他 provider:
# ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxx
EOF
chmod 600 ~/.hermes/.env
编辑 ~/.hermes/config.yaml 指定模型和 provider:
model:
default: "anthropic/claude-sonnet-4"
provider: "auto"
base_url: "https://openrouter.ai/api/v1"
步骤 3:运行 CLI 交互模式
# 启动交互式对话
hermes chat
进入交互模式后,尝试以下对话:
你好,请做个自我介绍
当前工作目录是什么?执行 ls 看看
步骤 4:掌握常用命令
在 CLI 交互模式中输入以下斜杠命令并观察行为:
| 命令 | 作用 | 验证方式 |
|---|---|---|
/commands | 列出所有可用命令 | 看到完整的命令列表 |
/new | 创建新会话 | Agent 提示新会话已创建 |
/compact | 手动压缩上下文 | Agent 确认上下文已压缩 |
/reset | 重置当前会话 | Agent 确认会话已重置 |
/resume | 恢复上次中断的会话 | Agent 恢复之前的对话上下文 |
实践流程:
> 你好,我叫小明
> /compact
> 你还记得我叫什么吗? (验证压缩后记忆是否保留)
> /new
> 你还记得我叫什么吗? (验证新会话记忆已清除)
> /commands
步骤 5:理解 ~/.hermes/ 目录结构
tree ~/.hermes/ -L 2
预期目录结构:
~/.hermes/
├── .env # 环境变量(API Key 等)
├── config.yaml # 主配置文件
├── SOUL.md # Agent 人格定义
├── memories/ # 持久记忆
│ ├── MEMORY.md
│ └── USER.md
├── skills/ # 技能目录
├── sessions/ # 会话数据库(SQLite)
├── logs/ # 日志目录
├── hooks/ # Hook 目录(Lab 4 使用)
├── cron/ # 定时任务数据
│ └── jobs.json
├── checkpoints/ # 检查点数据
└── bin/ # 自动安装的二进制工具
验证方法
# 确认 CLI 可正常对话
hermes chat -p "请回复:Hermes 安装成功"
# 检查配置加载是否正确
hermes status
常见问题排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
command not found: hermes | CLI 未加入 PATH | 检查 ~/.local/bin/hermes 是否存在,将其加入 PATH |
API key not configured | .env 文件缺失或 Key 无效 | 检查 ~/.hermes/.env 中的 Key 是否正确 |
ModuleNotFoundError | 依赖未完整安装 | 重新执行 uv pip install -e ".[all]" |
| 连接超时 | 网络问题或 base_url 配置错误 | 检查网络连通性,验证 base_url |
扩展挑战
- 尝试配置不同的 provider(如 Anthropic 直连、本地 Ollama),对比响应差异
- 修改
SOUL.md自定义 Agent 人格,观察行为变化 - 使用
hermes chat -p "一次性指令"执行非交互式单条命令
Lab 2:飞书接入 + Gateway 运行
Lab 目标:将 Hermes Agent 接入飞书平台,配置 Gateway 持久运行,理解会话管理机制。
预计时间:45 分钟
前置条件:Lab 1 已完成;拥有飞书开放平台管理员权限或开发者权限
步骤 1:创建飞书应用
- 访问飞书开放平台:https://open.feishu.cn/app
- 点击「创建自建应用」
- 填写应用名称(如 "Hermes Agent"),点击创建
- 在应用凭证页面记录:
- App ID(形如
cli_xxxxxxxx) - App Secret
- App ID(形如
- 在「事件订阅」中配置请求地址(如使用反向代理则填写公网地址)
- 在「权限管理」中开通以下权限:
im:message— 获取与发送消息im:message.group_at_msg— 接收群聊 @ 消息im:resource— 获取消息中的资源文件
- 发布应用版本,由管理员审批通过
步骤 2:配置飞书环境变量
# 编辑 .env 文件,追加飞书配置
cat >> ~/.hermes/.env << 'EOF'
# 飞书配置
FEISHU_APP_ID=cli_xxxxxxxxxxxxx
FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FEISHU_HOME_CHANNEL=oc_xxxxxxxxxxxxxxxx
EOF
说明:
FEISHU_APP_ID:飞书应用的 App IDFEISHU_APP_SECRET:飞书应用的 App SecretFEISHU_HOME_CHANNEL:默认通讯频道(群聊的 chat_id),可在飞书群设置中获取
验证环境变量加载:
hermes status
步骤 3:启动 Gateway
# 前台启动(适合调试)
hermes gateway run
# 后台启动(替换已有实例)
hermes gateway run --replace
首次启动时观察日志输出,确认以下信息:
[gateway] Loading platform adapters...
[gateway] Feishu adapter: connecting...
[gateway] Feishu WebSocket connected
[gateway] All adapters ready
步骤 4:在飞书中与 Agent 对话
- 打开配置的飞书群聊
- @Agent 或直接发送消息(取决于应用配置)
- 发送测试消息:
你好,请做个自我介绍 - 观察 Agent 是否正常回复
尝试以下操作观察会话行为:
帮我看看服务器磁盘空间 # 触发终端命令执行
/new # 重置会话
你还记得刚才的磁盘信息吗? # 验证会话已重置
步骤 5:配置 systemd 用户服务实现持久运行
# 创建 systemd 用户服务目录
mkdir -p ~/.config/systemd/user
# 创建服务文件
cat > ~/.config/systemd/user/hermes-gateway.service << 'EOF'
[Unit]
Description=Hermes Agent Gateway
After=network.target
[Service]
Type=simple
ExecStart=/opt/hermes-agent/venv/bin/hermes gateway run
Restart=on-failure
RestartSec=5
Environment=HERMES_GATEWAY_SESSION=1
Environment=HERMES_HOME=%h/.hermes
WorkingDirectory=/opt/hermes-agent
[Install]
WantedBy=default.target
EOF
注意:
ExecStart的路径需要根据实际hermes可执行文件位置调整。可通过which hermes获取。
启用并启动服务:
# 确保用户注销后服务仍运行
loginctl enable-linger $(whoami)
# 加载并启动服务
systemctl --user daemon-reload
systemctl --user enable hermes-gateway
systemctl --user start hermes-gateway
# 检查状态
systemctl --user status hermes-gateway
常用服务管理命令:
# 查看实时日志
journalctl --user -u hermes-gateway -f
# 重启服务
systemctl --user restart hermes-gateway
# 停止服务
systemctl --user stop hermes-gateway
步骤 6:理解会话管理
会话管理命令在飞书中同样有效:
| 命令 | 触发方式 | 效果 |
|---|---|---|
/new | 在飞书中发送 /new | 创建新会话,清空对话历史 |
/reset | 在飞书中发送 /reset | 重置当前会话状态 |
/compact | 在飞书中发送 /compact | 压缩当前上下文 |
/resume | 在飞书中发送 /resume | 恢复上次中断的会话 |
会话超时机制:Gateway 会话默认有 idle timeout。当用户长时间不活跃时,下次消息会自动触发新会话。活动追踪器监控 seconds_since_activity,Agent 依据此判断是否需要上下文恢复。
验证方法
# 1. 确认 systemd 服务运行正常
systemctl --user status hermes-gateway
# 2. 查看飞书 adapter 连接状态
journalctl --user -u hermes-gateway --since "5 min ago" | grep -i feishu
# 3. 在飞书中发送消息,确认 Agent 能正常回复
常见问题排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 飞书 WebSocket 连接失败 | App ID/Secret 错误 | 检查 .env 中的凭证,确认无多余空格 |
| 消息发送后无响应 | 应用未发布/未审批 | 在飞书开放平台确认应用已上线 |
| 服务启动后立即退出 | 配置文件格式错误 | 查看 journalctl 日志定位具体错误行 |
| 连接断开后不重连 | 网络不稳定 | 确认 Restart=on-failure 已配置,检查网络 |
扩展挑战
- 尝试同时接入多个平台(飞书 + Telegram),验证消息路由
- 配置
SOUL.md使 Agent 在飞书中使用更正式的语气 - 调整会话 idle timeout 参数,观察自动重置行为
Lab 3:编写自定义技能 + 部署 Web 应用
Lab 目标:创建自定义技能,通过飞书触发 Agent 自动构建并部署一个 Flask Web 应用。
预计时间:60 分钟
前置条件:Lab 2 已完成;服务器已安装 Nginx
步骤 1:了解技能目录结构
# 查看内置技能示例
ls /opt/hermes-agent/skills/software-development/
# 查看用户技能目录
ls ~/.hermes/skills/ 2>/dev/null || echo "目录尚不存在"
技能的标准目录结构:
~/.hermes/skills/
└── my-category/
└── my-skill/
├── SKILL.md # 主指令文件(必需)
├── references/ # 参考文档(可选)
└── templates/ # 模板文件(可选)
步骤 2:创建自定义技能
# 创建技能目录
mkdir -p ~/.hermes/skills/software-development/hello-web
# 创建 SKILL.md
cat > ~/.hermes/skills/software-development/hello-web/SKILL.md << 'SKILLEOF'
---
name: hello-web
description: 创建一个简单的 Flask Web 应用并部署到 Nginx
category: software-development
---
# Hello Web 技能
## 概述
当用户要求创建一个 Web 应用或网站时,执行以下步骤。
## 执行步骤
### 1. 创建 Flask 应用
在用户指定目录(默认 `/opt/hermes-agent/projects/hello-web`)创建以下文件:
**app.py**:
```python
from flask import Flask, jsonify
import socket
import datetime
app = Flask(__name__)
@app.route('/')
def hello():
return jsonify({
"message": "Hello from Hermes!",
"hostname": socket.gethostname(),
"timestamp": datetime.datetime.now().isoformat()
})
@app.route('/health')
def health():
return jsonify({"status": "ok"})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001)
2. 安装依赖
pip install flask gunicorn
3. 使用 Gunicorn 启动
cd /opt/hermes-agent/projects/hello-web
gunicorn --bind 0.0.0.0:5001 --workers 2 --daemon app:app
4. 配置 Nginx 反向代理
创建 Nginx 配置文件 /etc/nginx/sites-available/hello-web:
server {
listen 8080;
server_name _;
location / {
proxy_pass http://127.0.0.1:5001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
启用配置并重载 Nginx:
sudo ln -sf /etc/nginx/sites-available/hello-web /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
5. 验证部署
curl http://localhost:8080/
curl http://localhost:8080/health
注意事项
- 确保端口 5001 和 8080 未被占用
- 使用 Gunicorn 而非 Flask 开发服务器
- Nginx 配置中包含 proxy_set_header 确保真实 IP 传递 SKILLEOF
### 步骤 3:在飞书中触发技能执行
在飞书对话中发送:
请使用 hello-web 技能,帮我创建并部署一个 Flask Web 应用
Agent 会自动:
1. 调用 `skills_list()` 发现可用技能
2. 调用 `skill_view("hello-web")` 加载技能内容
3. 按照技能指令逐步执行
4. 创建文件、安装依赖、启动服务、配置 Nginx
### 步骤 4:验证 Web 应用可访问
```bash
# 测试应用接口
curl -s http://localhost:8080/ | python3 -m json.tool
# 预期输出:
# {
# "message": "Hello from Hermes!",
# "hostname": "...",
# "timestamp": "..."
# }
# 测试健康检查
curl -s http://localhost:8080/health
# 预期输出:{"status":"ok"}
步骤 5:理解技能加载机制
在 CLI 模式下验证技能发现:
hermes chat -p "列出所有可用的技能"
Agent 会调用 skills_list() 返回所有已注册技能的名称和描述。技能系统的渐进式披露架构:
Tier 0: skills_categories() → 分类名 + 描述
Tier 1: skills_list() → 技能名 + 描述(元数据级)
Tier 2: skill_view(name) → SKILL.md 完整内容
Tier 3: skill_view(name, file_path) → 关联文件内容
验证方法
# 1. 确认技能文件存在
cat ~/.hermes/skills/software-development/hello-web/SKILL.md | head -5
# 2. 确认 Web 应用运行
curl -s http://localhost:8080/health
# 3. 确认 Nginx 配置生效
sudo nginx -t
常见问题排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| Agent 未发现技能 | SKILL.md 位置或格式错误 | 确认路径为 ~/.hermes/skills/<category>/<name>/SKILL.md |
| Flask 应用启动失败 | 端口被占用 | lsof -i :5001 检查占用,更换端口 |
| Nginx 502 Bad Gateway | Gunicorn 未运行 | `ps aux |
| Frontmatter 解析失败 | YAML 格式错误 | 检查 --- 分隔符和缩进 |
扩展挑战
- 在技能中添加
references/目录,放入 API 设计文档,让 Agent 生成更复杂的应用 - 创建一个多步骤部署技能(含数据库初始化、静态文件处理)
- 使用
templates/目录存放配置文件模板,Agent 按模板生成实际配置
Lab 4:开发 i18n Hook(消息翻译)
Lab 目标:理解 Hook 系统架构,开发一个 i18n 国际化 Hook,实现 Agent 输出消息的自动翻译。
预计时间:45 分钟
前置条件:Lab 2 已完成;Gateway 以 systemd 方式运行
步骤 1:理解 Hook 系统架构
Hook 系统的核心组件:
~/.hermes/hooks/
└── hook-name/
├── HOOK.yaml # 元数据清单(必填)
└── handler.py # 处理函数(必填)
生命周期事件:
| 事件 | 触发时机 | 用途 |
|---|---|---|
gateway:startup | Gateway 启动 | 初始化、monkey-patch |
session:start | 新会话创建 | 加载用户偏好 |
session:end | 会话结束 | 清理资源 |
agent:start | Agent 开始处理消息 | 审计、计时 |
agent:end | Agent 完成处理 | 统计、通知 |
command:* | 斜杠命令执行 | 命令审计 |
步骤 2:创建 Hook 目录
mkdir -p ~/.hermes/hooks/i18n
步骤 3:编写 HOOK.yaml
cat > ~/.hermes/hooks/i18n/HOOK.yaml << 'EOF'
name: i18n
description: 自动将 Agent 输出翻译为中文
events:
- gateway:startup
EOF
此 Hook 只关注 gateway:startup 事件——在 Gateway 启动时执行 monkey-patch。
步骤 4:编写翻译字典
cat > ~/.hermes/hooks/i18n/translations.yaml << 'EOF'
zh-CN:
"New session started.": "新会话已创建。"
"Session reset.": "会话已重置。"
"Session resumed.": "会话已恢复。"
"Context compacted.": "上下文已压缩。"
"No previous session to resume.": "没有可恢复的会话。"
"I understand.": "我明白了。"
"Done.": "完成。"
"Working on it...": "正在处理..."
"Let me check that for you.": "我来帮你查一下。"
"I'll look into this.": "我会调查一下。"
"Task completed.": "任务已完成。"
"Error occurred.": "发生错误。"
EOF
步骤 5:编写 handler.py
cat > ~/.hermes/hooks/i18n/handler.py << 'PYEOF'
"""
i18n Hook — 自动将 Agent 发送的消息翻译为目标语言。
策略:在 gateway:startup 事件中,monkey-patch BasePlatformAdapter.send(),
在原始发送逻辑前插入翻译步骤。
调用链:
agent -> adapter.send() -> translate() -> adapter._original_send() -> 平台 API
"""
import logging
from pathlib import Path
import yaml
logger = logging.getLogger("hooks.i18n")
# --- 翻译字典 ---
_translation_dict: dict = {}
def _load_translations():
"""从 translations.yaml 加载翻译字典"""
global _translation_dict
try:
dict_path = Path(__file__).parent / "translations.yaml"
if dict_path.exists():
with open(dict_path, "r", encoding="utf-8") as f:
_translation_dict = yaml.safe_load(f) or {}
logger.info("i18n: Loaded %d languages from translations.yaml",
len(_translation_dict))
except Exception as e:
logger.warning("i18n: Failed to load translations: %s", e)
def translate(text: str, target_lang: str = "zh-CN") -> str:
"""翻译文本,支持精确匹配和子串匹配。
1. 精确匹配:text 在字典中直接命中
2. 子串匹配:遍历字典 key,替换 text 中的匹配项
"""
if not text or not _translation_dict:
return text
lang_dict = _translation_dict.get(target_lang, {})
if not lang_dict:
return text
# 1. 精确匹配
if text in lang_dict:
return lang_dict[text]
# 2. 子串匹配(按长度降序,优先匹配更长的子串)
translated = text
for source, target in sorted(lang_dict.items(), key=lambda x: -len(x[0])):
if source in translated:
translated = translated.replace(source, target)
return translated
def handle(event_type: str, context: dict) -> None:
"""Hook 入口函数。仅在 gateway:startup 时执行 monkey-patch。"""
if event_type != "gateway:startup":
return
_load_translations()
if not _translation_dict:
logger.warning("i18n: No translations loaded, skipping monkey-patch")
return
# 动态导入 BasePlatformAdapter(必须在 Gateway 运行时才可用)
try:
from gateway.platforms.base import BasePlatformAdapter
except ImportError:
logger.warning("i18n: Cannot import BasePlatformAdapter, skipping")
return
# 保存原始 send 方法
_original_send = BasePlatformAdapter.send
# 创建 monkey-patched 版本
async def _translated_send(self, chat_id, content, reply_to=None, metadata=None):
"""在发送前翻译文本内容"""
if isinstance(content, str):
content = translate(content)
elif isinstance(content, dict) and "text" in content:
content["text"] = translate(content["text"])
return await _original_send(self, chat_id, content, reply_to, metadata)
# 应用 monkey-patch
BasePlatformAdapter.send = _translated_send
logger.info("i18n: Monkey-patched BasePlatformAdapter.send() successfully")
PYEOF
步骤 6:重启 Gateway 并测试
# 重启 Gateway 使 Hook 生效
systemctl --user restart hermes-gateway
# 查看启动日志,确认 Hook 加载
journalctl --user -u hermes-gateway --since "1 min ago" | grep -i "i18n\|hook"
在飞书中测试:
/new
如果 Hook 工作正常,Agent 应回复翻译后的中文消息(如 "新会话已创建。"),而非英文的 "New session started."。
步骤 7:调试技巧
# 查看 Gateway 完整日志
journalctl --user -u hermes-gateway -f
# 只看 Hook 相关日志
journalctl --user -u hermes-gateway | grep "hooks"
# 查看 Agent 日志文件
tail -f ~/.hermes/logs/agent.log
# 查看 Gateway 日志文件
tail -f ~/.hermes/logs/gateway.log
# 查看 errors 日志
tail -f ~/.hermes/logs/errors.log
验证方法
# 1. 确认 Hook 文件结构完整
ls -la ~/.hermes/hooks/i18n/
# 应显示 HOOK.yaml, handler.py, translations.yaml
# 2. 检查 Hook 加载日志
journalctl --user -u hermes-gateway | grep "i18n"
# 应看到 "Monkey-patched BasePlatformAdapter.send() successfully"
# 3. 在飞书中发送 /new,观察回复是否为中文
常见问题排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| Hook 未加载 | HOOK.yaml 格式错误 | 检查 YAML 语法,确认 events 列表非空 |
| handler.py 报错 | 函数名不是 handle | 确认顶层函数名为 handle,签名为 (event_type, context) |
| 翻译不生效 | translations.yaml 路径错误 | 确认与 handler.py 在同一目录 |
| Gateway 启动失败 | handler.py 语法错误 | python3 -c "exec(open('~/.hermes/hooks/i18n/handler.py').read())" 检查 |
| Monkey-patch 失败 | 导入路径变更 | 检查 from gateway.platforms.base import BasePlatformAdapter 是否正确 |
扩展挑战
- 在翻译字典中添加更多 Agent 常用输出,提高翻译覆盖率
- 修改 Hook 支持多语言切换(根据用户或群聊配置选择目标语言)
- 添加正则匹配翻译规则,处理动态内容(如日期、数字格式)
Lab 5:子 Agent 并行开发 + 审查流程
Lab 目标:理解子 Agent 委派机制,配置 delegation 参数,触发并行任务执行,体验代码审查流程。
预计时间:60 分钟
前置条件:Lab 2 已完成;理解 Agent 的工具调用循环
步骤 1:理解子 Agent 委派架构
核心参数(源码位于 tools/delegate_tool.py):
DELEGATE_BLOCKED_TOOLS = frozenset([
"delegate_task", # 禁止递归委派
"clarify", # 禁止与用户交互
"memory", # 禁止写入共享记忆
"send_message", # 禁止跨平台发消息
"execute_code", # 禁止代码执行(应逐步推理)
])
_DEFAULT_MAX_CONCURRENT_CHILDREN = 3 # 最大并行子 Agent 数
MAX_DEPTH = 2 # 委派深度限制
DEFAULT_MAX_ITERATIONS = 50 # 子 Agent 最大迭代次数
子 Agent 的隔离设计:
- 独立的对话上下文(
skip_context_files=True) - 不加载 SOUL.md / AGENTS.md / 记忆文件
- 禁止与用户交互(
clarify_callback=None) - 静默执行(
quiet_mode=True) - 共享会话存储(只读)
步骤 2:配置 delegation 参数
编辑 ~/.hermes/config.yaml,添加或修改以下配置:
agent:
delegation:
max_iterations: 50 # 子 Agent 最大迭代次数
max_concurrent_children: 3 # 最大并行子 Agent 数
也可以通过环境变量调整:
# 这些参数通常在源码中硬编码,高级用户可直接修改 delegate_tool.py
# 或在 prompt 中指导 Agent 使用特定参数
步骤 3:触发子 Agent 委派
在飞书中发送一个复杂任务,要求并行处理:
请帮我同时完成以下任务:
1. 在 /tmp/task1/ 目录下创建一个 Python 计算器模块,包含加减乘除功能
2. 在 /tmp/task2/ 目录下创建一个 Python 单元测试文件,测试上面的计算器
3. 分析当前服务器的 CPU、内存、磁盘使用情况,生成报告保存到 /tmp/sys-report.txt
观察 Agent 的行为:
- Agent 分析任务,判断可以拆分为子任务
- 调用
delegate_task创建子 Agent - 子 Agent 并行执行各自的任务
- 主 Agent 收集结果并汇总
步骤 4:观察并行执行
# 在另一个终端监控日志
journalctl --user -u hermes-gateway -f | grep -i "delegate\|subagent\|child"
日志中应能看到:
[delegate] Spawning child agent for: 创建 Python 计算器模块
[delegate] Spawning child agent for: 创建单元测试文件
[delegate] Spawning child agent for: 分析服务器资源
[delegate] Child 1 completed: ...
[delegate] Child 2 completed: ...
[delegate] Child 3 completed: ...
验证子 Agent 的产出:
# 检查任务产出
ls -la /tmp/task1/
ls -la /tmp/task2/
cat /tmp/sys-report.txt
步骤 5:体验代码审查流程
在飞书中发送:
请审查 /tmp/task1/ 目录下的计算器代码和 /tmp/task2/ 目录下的测试代码。
使用两个子 Agent:一个负责代码质量审查,另一个负责测试覆盖率审查。
最后汇总审查意见。
此任务会触发「双 Agent review」模式:
- 子 Agent A:审查代码质量(命名规范、异常处理、代码结构)
- 子 Agent B:审查测试覆盖率(边界条件、异常路径)
- 主 Agent:汇总两份审查报告,给出改进建议
步骤 6:理解 IterationBudget 限制
主 Agent 的 IterationBudget 默认为 90 次迭代。子 Agent 默认为 50 次。当 Agent 达到迭代上限时,会输出一条 "iteration budget exhausted" 消息。
# 查看 Agent 日志中的迭代信息
grep -i "iteration" ~/.hermes/logs/agent.log | tail -20
验证方法
# 1. 确认子 Agent 产出物存在
test -f /tmp/task1/calculator.py && echo "calculator.py exists"
test -f /tmp/task2/test_calculator.py && echo "test exists"
test -f /tmp/sys-report.txt && echo "report exists"
# 2. 运行子 Agent 创建的测试
cd /tmp && python3 -m pytest task2/ -v
# 3. 查看 Agent 日志中的委派记录
grep "delegate" ~/.hermes/logs/agent.log | tail -10
常见问题排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| Agent 不委派,自己全部执行 | 任务不够复杂或提示不够明确 | 明确要求"并行"处理,拆分任务列表 |
| 子 Agent 报错 "clarify not available" | 子 Agent 尝试向用户提问 | 这是预期行为,子 Agent 禁止交互 |
| 达到 MAX_DEPTH 限制 | 子 Agent 尝试创建孙 Agent | MAX_DEPTH=2,子 Agent 不能再委派 |
| 并行数不足 | 超过 max_concurrent_children | 调整配置或减少并行任务数 |
扩展挑战
- 发送一个包含 5 个子任务的复杂项目,观察 Agent 如何排队和调度
- 尝试让 Agent 对代码进行修改后再审查,体验「开发-审查」闭环
- 监控子 Agent 的 token 消耗,分析并行执行的成本效益
Lab 6:生产部署 + 安全加固 + 监控
Lab 目标:对 Hermes Agent 进行生产级安全加固,配置 HTTPS、日志管理、监控告警,掌握更新流程。
预计时间:45 分钟
前置条件:Lab 2-4 已完成;拥有域名和 SSL 证书(或使用自签名证书)
步骤 1:安全配置
1.1 审批模式设置
编辑 ~/.hermes/config.yaml:
security:
approvals:
mode: "ask" # ask(默认)/ auto-approve / deny
redact_secrets: true # 自动从日志和输出中脱敏 API Key
审批模式说明:
| 模式 | 行为 | 适用场景 |
|---|---|---|
ask | 危险命令需用户确认 | CLI 模式、开发环境 |
auto-approve | 自动批准所有命令 | 受控生产环境、CI/CD |
deny | 拒绝所有危险命令 | 高安全要求环境 |
1.2 凭证管理
# 确认 .env 权限(仅当前用户可读写)
chmod 600 ~/.hermes/.env
ls -la ~/.hermes/.env
# 应显示:-rw------- 1 user user ...
# 确认 .env 不在 git 跟踪中
echo ".env" >> ~/.hermes/.gitignore
API Key 轮转策略:
# 1. 在 provider 控制台生成新 Key
# 2. 更新 .env 文件
# 3. 重启 Gateway
systemctl --user restart hermes-gateway
# 4. 在 provider 控制台吊销旧 Key
1.3 危险命令防护
系统内置的 DANGEROUS_PATTERNS 会自动拦截以下类别的命令:
- 递归删除:
rm -rf - 权限变更:
chmod 777 - 远程执行:
curl | sh - 系统破坏:
mkfs,dd - SQL 危险操作:
DROP TABLE,DELETE without WHERE
验证防护生效:
hermes chat -p "执行 rm -rf /tmp/test"
# Agent 应提示此命令被标记为危险操作,需要确认
步骤 2:日志管理
2.1 配置日志级别
编辑 ~/.hermes/config.yaml:
logging:
level: INFO
file_level: DEBUG # 文件记录更详细
console_level: WARNING # 控制台保持简洁
2.2 配置日志轮转
sudo tee /etc/logrotate.d/hermes << 'EOF'
/home/*/.hermes/logs/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
}
EOF
手动触发轮转测试:
sudo logrotate -d /etc/logrotate.d/hermes # dry-run 测试
关键日志文件:
| 文件 | 内容 |
|---|---|
~/.hermes/logs/gateway.log | Gateway 启动、请求处理、平台事件 |
~/.hermes/logs/agent.log | Agent 迭代、工具调用、压缩事件 |
~/.hermes/logs/cron.log | 定时任务执行记录 |
步骤 3:Nginx HTTPS 配置
# 安装 certbot(Let's Encrypt)
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
# 使用已有域名申请证书
# sudo certbot --nginx -d hermes.example.com
# 或使用自签名证书(仅测试用)
sudo openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout /etc/ssl/private/hermes-selfsigned.key \
-out /etc/ssl/certs/hermes-selfsigned.crt \
-subj "/CN=hermes.local"
创建 Nginx HTTPS 配置:
sudo tee /etc/nginx/sites-available/hermes-gateway << 'EOF'
server {
listen 443 ssl http2;
server_name hermes.example.com;
# SSL 证书(替换为实际路径)
ssl_certificate /etc/ssl/certs/hermes-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/hermes-selfsigned.key;
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# SSL 加固
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Agent 响应可能较慢,设置较长超时
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name hermes.example.com;
return 301 https://$host$request_uri;
}
EOF
# 启用配置
sudo ln -sf /etc/nginx/sites-available/hermes-gateway /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
步骤 4:监控配置
4.1 磁盘空间监控
# 创建简单监控脚本
sudo tee /usr/local/bin/hermes-health-check << 'SCRIPT'
#!/bin/bash
# Hermes Agent 健康检查脚本
THRESHOLD=80
# 检查磁盘空间
DISK_USAGE=$(df -h ~/.hermes | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt "$THRESHOLD" ]; then
echo "WARNING: Disk usage at ${DISK_USAGE}% (threshold: ${THRESHOLD}%)"
fi
# 检查会话数据库大小
SESSION_SIZE=$(du -sm ~/.hermes/sessions/ 2>/dev/null | awk '{print $1}')
if [ -n "$SESSION_SIZE" ] && [ "$SESSION_SIZE" -gt 100 ]; then
echo "WARNING: Session database is ${SESSION_SIZE}MB (threshold: 100MB)"
fi
# 检查日志目录大小
LOG_SIZE=$(du -sm ~/.hermes/logs/ 2>/dev/null | awk '{print $1}')
if [ -n "$LOG_SIZE" ] && [ "$LOG_SIZE" -gt 500 ]; then
echo "WARNING: Log directory is ${LOG_SIZE}MB (threshold: 500MB)"
fi
# 检查 Gateway 进程
if ! systemctl --user is-active --quiet hermes-gateway 2>/dev/null; then
echo "CRITICAL: hermes-gateway service is not running"
fi
echo "Health check completed at $(date)"
SCRIPT
sudo chmod +x /usr/local/bin/hermes-health-check
添加 Cron 定时检查:
# 每 30 分钟执行一次健康检查
(crontab -l 2>/dev/null; echo "*/30 * * * * /usr/local/bin/hermes-health-check >> ~/.hermes/logs/health.log 2>&1") | crontab -
4.2 资源指标参考
| 指标 | 阈值 | 检查方式 |
|---|---|---|
| 磁盘空间 | > 80% 告警 | df -h ~/.hermes |
| 会话数据库 | > 100MB 清理 | du -sh ~/.hermes/sessions/ |
| 日志目录 | > 500MB 轮转 | du -sh ~/.hermes/logs/ |
| 检查点数据 | > 1GB 清理 | du -sh ~/.hermes/checkpoints/ |
| 内存使用 | 根据模型上下文调整 | `ps aux |
步骤 5:更新流程
标准更新流程(此流程应固化到运维文档中):
# 1. 查看当前版本
hermes --version
# 2. 拉取最新代码
cd /opt/hermes-agent
git pull origin main
# 3. 更新依赖
uv pip install -e ".[all]"
# 4. 检查配置兼容性(新版本可能引入新配置项)
git diff HEAD~1 -- cli-config.yaml.example
# 5. 备份当前配置
cp ~/.hermes/config.yaml ~/.hermes/config.yaml.bak
# 6. 重启 Gateway
systemctl --user restart hermes-gateway
# 7. 验证版本
hermes --version
# 8. 验证服务正常
systemctl --user status hermes-gateway
hermes-health-check
步骤 6:生产环境 Checklist
部署前逐项确认:
-
.env文件权限为 600,且不在版本控制中 -
config.yaml中redact_secrets: true已启用 - 审批模式已根据环境正确设置
- SSL 证书已配置且未过期
- Nginx 安全头已添加(X-Frame-Options, X-Content-Type-Options)
- 日志轮转已配置(
/etc/logrotate.d/hermes) - systemd 用户服务已启用
loginctl enable-linger - 健康检查脚本已配置定时执行
- API Key 轮转流程已文档化
- 磁盘空间告警阈值已设定
- 更新流程已验证(git pull + pip install + restart)
- 备份策略已建立(config.yaml、.env、sessions/)
验证方法
# 1. 验证 HTTPS 可访问(替换为实际域名)
curl -k https://hermes.example.com/ 2>/dev/null | head -5
# 2. 验证日志轮转配置
sudo logrotate -d /etc/logrotate.d/hermes
# 3. 运行健康检查
hermes-health-check
# 4. 验证危险命令防护
hermes chat -p "执行 rm -rf /"
# 5. 验证服务持久化
systemctl --user status hermes-gateway
常见问题排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| SSL 证书验证失败 | 自签名证书或过期 | 使用 Let's Encrypt 或更新证书 |
| Nginx 502 错误 | Gateway 进程未运行 | systemctl --user start hermes-gateway |
| 日志文件过大 | logrotate 未配置 | 确认 /etc/logrotate.d/hermes 存在且权限正确 |
| 更新后配置不兼容 | 新版本引入配置变更 | 对比 cli-config.yaml.example,更新 config.yaml |
| 磁盘空间不足 | 日志或会话数据积累 | 手动清理:find ~/.hermes/logs/ -name "*.log" -mtime +30 -delete |
| 服务重启失败 | 依赖更新不完整 | 重新执行 uv pip install -e ".[all]" |
扩展挑战
- 配置 Prometheus + Grafana 对 Hermes 进行指标采集和可视化
- 实现自动化的 API Key 轮转脚本
- 配置飞书机器人将健康检查告警推送到运维群
- 编写备份恢复脚本,定期备份
~/.hermes/关键数据
附录
A. 环境变量速查
| 环境变量 | 用途 | 示例 |
|---|---|---|
OPENROUTER_API_KEY | OpenRouter API Key | sk-or-v1-xxx |
ANTHROPIC_API_KEY | Anthropic 直连 API Key | sk-ant-xxx |
FEISHU_APP_ID | 飞书应用 App ID | cli_xxxxxxxx |
FEISHU_APP_SECRET | 飞书应用 App Secret | xxxxxxx |
FEISHU_HOME_CHANNEL | 飞书默认频道 ID | oc_xxxxxxxx |
HERMES_LOG_LEVEL | 日志级别 | DEBUG / INFO |
HERMES_HOME | 数据目录 | ~/.hermes |
B. 常用运维命令速查
# 服务管理
systemctl --user status hermes-gateway # 查看状态
systemctl --user restart hermes-gateway # 重启服务
systemctl --user stop hermes-gateway # 停止服务
journalctl --user -u hermes-gateway -f # 实时日志
# 健康检查
hermes status # Agent 状态
hermes --version # 版本信息
hermes-health-check # 自定义健康检查
# 更新流程
cd /opt/hermes-agent && git pull # 拉取代码
uv pip install -e ".[all]" # 更新依赖
systemctl --user restart hermes-gateway # 重启服务
# 故障排查
tail -f ~/.hermes/logs/agent.log # Agent 日志
tail -f ~/.hermes/logs/gateway.log # Gateway 日志
tail -f ~/.hermes/logs/errors.log # 错误日志
C. 实验环境清理
完成所有实验后,如需清理环境:
# 停止服务
systemctl --user stop hermes-gateway
systemctl --user disable hermes-gateway
# 清理 Lab 3 产物
pkill -f "gunicorn.*app:app" 2>/dev/null
sudo rm -f /etc/nginx/sites-available/hello-web
sudo rm -f /etc/nginx/sites-enabled/hello-web
sudo systemctl reload nginx
# 清理 Lab 5 产物
rm -rf /tmp/task1 /tmp/task2 /tmp/sys-report.txt
# 可选:清理 Hermes 数据(谨慎操作)
# rm -rf ~/.hermes/sessions/
# rm -rf ~/.hermes/logs/