第1章:架构与入口 —— 一个 Agent 产品为什么要分成这么多运行时
一、先不看文件:Hermes 到底是什么
一句话:Hermes 是一个把“模型调用”包装成长期可运行工作的 Agent Runtime。
它同时面对三类输入:
- CLI/TUI 里的同步或交互式输入;
- Telegram、Discord、Slack、WhatsApp、Signal、Webhook 等平台的异步输入;
- ACP、API Server、批处理和定时任务产生的程序化输入。
它同时面对三类副作用:调用外部模型、执行本地或远程命令、向外部平台发送消息。于是代码不能只围绕一次函数调用组织,而要围绕状态、恢复、授权、并发和投递组织。
二、五层结构:按“谁拥有哪种状态”来分
| 层 | 主要职责 | 关键模块 |
|---|---|---|
| 入口层 | 解析命令、装载配置、建立交互或服务 | hermes_cli/main.py、cli.py、acp_adapter/entry.py |
| Agent 层 | 维护模型、消息、工具、上下文和一次运行 | run_agent.py、agent/ |
| 能力层 | 注册工具、加载技能/插件、调用 MCP、委托子 Agent | tools/、skills/、plugins/ |
| 连接层 | 连接外部平台,做 session 映射、流式投递和路由 | gateway/ |
| 运维层 | 让任务可以远程、定时、批量、可观测地运行 | cron/、tools/environments/、batch_runner.py |
这不是严格的单向分层。比如 Gateway 会创建 Agent,Agent 又通过 gateway.session_context 获得平台上下文;重要的是依赖集中在契约上,而不是每个平台各自复制一套 Agent Loop。
三、入口:为什么 hermes 不是 run_agent.py
pyproject.toml 的三个 console script 是理解项目的第一把钥匙:
hermes = "hermes_cli.main:main"
hermes-agent = "run_agent:main"
hermes-acp = "acp_adapter.entry:main"
这表示“产品入口”和“可复用 Agent”被有意分开:
hermes_cli.main:main负责安装后的用户体验、子命令、配置向导、gateway 生命周期和 TUI 选择。run_agent.AIAgent是可以被 CLI、Gateway、Cron、批处理和 ACP 共同调用的运行时对象。acp_adapter.entry:main把同一个运行时暴露给编辑器协议,避免编辑器客户端必须理解 Hermes 的内部工具和 session 细节。
hermes_cli/main.py 还把 hermes_bootstrap 放在最早的位置,用于 Windows UTF-8 stdio、早期恢复和窗口抑制。这是一个很实用的原则:入口最早做平台修复,业务层不再到处判断平台。
四、一次 CLI 请求的最短路径
hermes
→ hermes_cli.main.main()
→ CLI/TUI 选择与配置加载
→ hermes_cli.oneshot / cli.py
→ AIAgent(...)
→ AIAgent.run_conversation()
→ agent.conversation_loop.run_conversation()
→ provider transport + tool executor
最值得注意的是 AIAgent 的位置:它不是“单纯的 API client”。在 run_agent.py:409 附近可以看到它集中持有模型信息、会话标识、工具状态、上下文引擎、记忆管理器、执行环境、回调、重试和清理逻辑;run_conversation 在 run_agent.py:7153 附近把这些状态组合成一次运行。
五、为什么不把所有代码放进一个 Agent 类
Hermes 的拆分大致对应四种变化速度:
- 模型协议变化:
agent/transports/、各种 provider adapter; - 工具与安全变化:
tools/、agent/tool_guardrails.py、审批与 checkpoint; - 上下文策略变化:
agent/context_engine.py、context_compressor.py、prompt_builder.py; - 入口和投递变化:
gateway/、hermes_cli/、acp_adapter/。
如果把它们揉在一起,任意一个平台接入都会改变核心循环;现在核心循环只需要依赖诸如“发送请求”“获得 tool call”“追加结果”“持久化 session”这类边界。
六、从仓库规模反推设计重点
源码中最大的目录并不是 agent/,而是 tests/:约 2,625 个 Python 测试文件。它说明 Hermes 的复杂度不是来自单一算法,而来自组合状态:平台、模型、工具、压缩、并发、凭证、操作系统和恢复路径的交叉组合。
因此,读 Hermes 的正确单位不是“文件”,而是“生命周期”:
进程启动 → session 建立 → turn 开始 → API 请求 → tool 执行
→ 结果持久化 → 继续/压缩/停止 → 投递 → session 关闭
下一章会从这条生命周期中最核心的中段开始:Agent Loop。