AgentHarness 课程

第十一篇:动手实验手册

2.7万字·1小时9分钟·
6个渐进式Lab

概述

本手册包含 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: hermesCLI 未加入 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:创建飞书应用

  1. 访问飞书开放平台:https://open.feishu.cn/app
  2. 点击「创建自建应用」
  3. 填写应用名称(如 "Hermes Agent"),点击创建
  4. 在应用凭证页面记录:
    • App ID(形如 cli_xxxxxxxx
    • App Secret
  5. 在「事件订阅」中配置请求地址(如使用反向代理则填写公网地址)
  6. 在「权限管理」中开通以下权限:
    • im:message — 获取与发送消息
    • im:message.group_at_msg — 接收群聊 @ 消息
    • im:resource — 获取消息中的资源文件
  7. 发布应用版本,由管理员审批通过

步骤 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 ID
  • FEISHU_APP_SECRET:飞书应用的 App Secret
  • FEISHU_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 对话

  1. 打开配置的飞书群聊
  2. @Agent 或直接发送消息(取决于应用配置)
  3. 发送测试消息:你好,请做个自我介绍
  4. 观察 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 GatewayGunicorn 未运行`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:startupGateway 启动初始化、monkey-patch
session:start新会话创建加载用户偏好
session:end会话结束清理资源
agent:startAgent 开始处理消息审计、计时
agent:endAgent 完成处理统计、通知
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 的行为:

  1. Agent 分析任务,判断可以拆分为子任务
  2. 调用 delegate_task 创建子 Agent
  3. 子 Agent 并行执行各自的任务
  4. 主 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」模式:

  1. 子 Agent A:审查代码质量(命名规范、异常处理、代码结构)
  2. 子 Agent B:审查测试覆盖率(边界条件、异常路径)
  3. 主 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 尝试创建孙 AgentMAX_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.logGateway 启动、请求处理、平台事件
~/.hermes/logs/agent.logAgent 迭代、工具调用、压缩事件
~/.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.yamlredact_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_KEYOpenRouter API Keysk-or-v1-xxx
ANTHROPIC_API_KEYAnthropic 直连 API Keysk-ant-xxx
FEISHU_APP_ID飞书应用 App IDcli_xxxxxxxx
FEISHU_APP_SECRET飞书应用 App Secretxxxxxxx
FEISHU_HOME_CHANNEL飞书默认频道 IDoc_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/