Hermes Agent 项目学习要点
目标:快速建立对 Hermes Agent 的代码地图、核心流程和常见改动入口的理解。 配套阅读:
learn/hermes-architecture.md偏架构全景;本文偏学习路线与开发抓手。
1. 先建立一个整体心智模型
Hermes Agent 可以理解成四层:
- 入口层:CLI、TUI、Gateway、Dashboard、Python library。
- 核心 Agent 层:
AIAgent负责组 prompt、调用模型、处理 tool call、维护会话循环。 - 能力层:tools、toolsets、skills、memory、MCP、plugins、cron、delegation。
- 持久化与运行环境层:SQLite 会话库、
~/.hermes配置目录、profile 隔离、日志、终端后端。
最重要的一句话:用户输入最终都会被整理成 OpenAI 风格 messages,送进 AIAgent.run_conversation();模型返回 tool calls 时,经 model_tools.handle_function_call() 分发到具体工具。
2. 推荐阅读顺序
第一轮:能跑起来
README.zh-CN.md:了解产品定位、安装、主要命令。pyproject.toml:看依赖分层、extras、命令入口。hermes_cli/main.py:理解hermes命令如何进入不同子命令。cli.py:理解交互式 CLI 的主流程。
重点问题:
hermes、hermes-agent、hermes-acp分别从哪里进入?- CLI 命令和 slash command 是怎么分发的?
- 哪些依赖是 core,哪些是按功能懒加载或 extra?
第二轮:看懂一次对话
run_agent.pyAIAgent.__init__AIAgent.run_conversationAIAgent.chat
model_tools.pyhandle_function_call- tool schema 获取与工具调用分发
tools/registry.pydiscover_builtin_toolsToolRegistry.registerget_definitions
toolsets.py_HERMES_CORE_TOOLS- toolset 暴露规则
重点问题:
- system prompt、history、memory、skills 在什么时候拼进 messages?
- 模型返回
tool_calls后,代码如何找到对应 handler? - 为什么一个 tool 文件自动注册后,还需要出现在 toolset 中才会暴露给 Agent?
第三轮:理解状态与配置
hermes_cli/config.pyDEFAULT_CONFIGOPTIONAL_ENV_VARSload_config
cli.pyload_cli_configsave_config_value
gateway/run.py与gateway/config.py- Gateway 对配置的读取路径不同于 CLI。
hermes_constants.pyget_hermes_homedisplay_hermes_home
hermes_state.pySessionDB- SQLite + FTS5 会话存储。
重点问题:
config.yaml和.env各自应该放什么?- profile 隔离如何影响
~/.hermes路径? - CLI、普通子命令、Gateway 三条配置加载路径有什么差异?
第四轮:看扩展系统
skills/与optional-skills/- 内置技能与可选技能的区别。
tools/skills_hub.py- optional skills 的安装来源。
hermes_cli/plugins.py- general plugin discovery。
plugins/model-providers/- provider 插件如何注册
ProviderProfile。
- provider 插件如何注册
agent/memory_manager.py与plugins/memory/- memory provider 抽象与内置后端。
mcp_serve.py与 MCP 相关工具- 外部 MCP 工具如何接入。
重点问题:
- 普通自定义能力优先走 plugin,什么时候才改 core tools?
- skill 是如何以用户消息形式注入,而不是 system prompt?
- model provider 插件为什么是独立 discovery,而不是普通 PluginManager 直接 import?
第五轮:看界面与多平台
hermes_cli/commands.pyCOMMAND_REGISTRY是 slash command 的单一事实源。
ui-tui/src/- Ink TUI 前端。
tui_gateway/server.py- TUI 与 Python Agent 的 JSON-RPC 桥。
gateway/run.py- 消息网关主调度。
gateway/platforms/- Telegram、Discord、Slack 等平台适配器。
web/与hermes_cli/web_server.py- Dashboard,其中
/chat嵌入真实hermes --tui,不要另写一套聊天界面。
- Dashboard,其中
重点问题:
- 一个 slash command 如何同时影响 CLI、Gateway help、Telegram menu、Slack routing 和自动补全?
- Dashboard 为什么复用 TUI,而不是在 React 里重写 chat?
- 平台消息如何转成统一事件再交给核心 Agent?
3. 核心调用链
CLI 对话链路
hermes
-> hermes_cli/main.py
-> cli.py / HermesCLI
-> AIAgent.run_conversation()
-> model client chat completion
-> model_tools.handle_function_call()
-> tools/registry.py 中的 handler
-> tool result append 回 messages
-> 继续模型调用或输出 final response
TUI 链路
hermes --tui
-> Node / Ink UI
-> stdio JSON-RPC
-> tui_gateway/server.py
-> AIAgent + tools + sessions
-> events 回传给 Ink 渲染
Gateway 链路
platform message
-> gateway/platforms/<platform>
-> gateway/run.py
-> session / user / platform context
-> AIAgent.run_conversation()
-> platform adapter send response
Tool 注册与暴露链路
tools/registry.py
<- tools/*.py 顶层 registry.register()
<- model_tools.py 触发 discover_builtin_tools()
<- toolsets.py 决定哪些工具集暴露
<- run_agent.py 把 tool schemas 传给模型
4. 常见开发任务入口
新增 slash command
改动入口:
hermes_cli/commands.py:添加CommandDef。cli.py:在HermesCLI.process_command()中处理。gateway/run.py:如果消息平台也支持,添加 gateway handler。
注意点:
- alias 只需要加在
CommandDef.aliases。 - help、autocomplete、Telegram menu、Slack mapping 都从
COMMAND_REGISTRY派生。 cli_only、gateway_only、gateway_config_gate决定不同入口是否可见。
新增 core tool
优先判断:如果只是本地自用或第三方能力,优先写 plugin,不要改 core。
必须改 core 时:
tools/<name>.py:顶层调用registry.register()。toolsets.py:把工具挂到对应 toolset 或_HERMES_CORE_TOOLS。
注意点:
- handler 必须返回 JSON 字符串。
- schema 中涉及 Hermes home 路径时,用
display_hermes_home()。 - 持久化状态路径用
get_hermes_home(),不要硬编码~/.hermes。 check_fn会被 TTL 缓存,配置变更后必要时调用 cache invalidation。
新增配置项
改动入口:
hermes_cli/config.py的DEFAULT_CONFIG。- 如为 secret/API key,加到
OPTIONAL_ENV_VARS,放.env。 - 如为普通设置,放
config.yaml,不要放.env。 - Gateway 需要读取时,检查
gateway/run.py与gateway/config.py是否走了同一套默认值。
注意点:
- 添加普通新 key 通常不需要 bump
_config_version。 - 只有重命名、结构迁移、语义迁移时才需要 bump。
新增 model provider
改动入口:
plugins/model-providers/<name>/__init__.py- 注册
ProviderProfile(...)。
注意点:
- model provider 是懒 discovery。
- 用户同名 provider 插件可覆盖 bundled provider。
- 普通 PluginManager 不直接 import model provider,避免重复注册。
新增 memory provider
原则:
- 不再新增 in-tree
plugins/memory/<name>/。 - 新 memory backend 应作为独立 plugin repo 安装到
~/.hermes/plugins/或通过 pip entry point 暴露。
需要理解:
agent/memory_provider.py定义 ABC。agent/memory_manager.py负责 orchestrate。post_setup()可接入 setup wizard。
修改 Dashboard Chat
关键规则:
- 主聊天体验属于 embedded
hermes --tui。 - Dashboard
/chat通过 PTY bridge 嵌入真实 TUI。 - 不要在 React 中重写 transcript、composer、slash command 流程。
可做的:
- 侧边栏、状态面板、工具调用 inspector、模型选择弹窗等辅助 UI。
- 新聊天能力优先扩展 Ink TUI,让 Dashboard 自动受益。
5. 测试与验证抓手
常用命令:
scripts/run_tests.sh
python -m pytest tests/ -q
python -m pytest tests/<target_test>.py -q
cd ui-tui && npm run type-check
cd ui-tui && npm test
cd ui-tui && npm run build
选择测试范围:
- 改 tool registry / toolsets:跑 registry、model_tools、tool exposure 相关测试。
- 改 slash command:跑 commands、CLI dispatch、gateway command 相关测试。
- 改 config:跑 config merge、migration、gateway config 相关测试。
- 改 TUI:跑
ui-tui的 type-check、vitest、build。 - 改 Gateway:跑目标 platform adapter 测试,再跑 shared gateway tests。
开发时优先用 rg 定位:
rg -n "COMMAND_REGISTRY|resolve_command|process_command"
rg -n "registry.register|handle_function_call|get_definitions"
rg -n "DEFAULT_CONFIG|load_config|load_cli_config"
rg -n "get_hermes_home|display_hermes_home"
6. 容易踩坑的地方
-
工具自动发现不等于自动暴露。
tools/*.py被 import 后会注册 schema,但 agent 是否看到这个 tool 由 toolset 决定。 -
CLI 与 Gateway 配置加载路径不同。 CLI 常走
load_cli_config()或load_config();Gateway 还会直接读 YAML。新增配置时两边都要核对。 -
.env只放 secrets。 超时、路径、开关、阈值这类非 secret 应进入config.yaml。 -
profile-aware path 很重要。 代码里不要直接写
Path.home() / ".hermes",应使用get_hermes_home()。 -
依赖策略要保守。 当前
pyproject.toml里 core 依赖使用精确 pin,并通过uv.lock固化解析结果。新增依赖前先判断是否真的属于 core;provider/search/TTS/image/messaging 等可选能力通常应该放 extras 或 lazy install。 -
插件不要硬改 core。 如果 plugin 需要 core 没暴露的能力,优先扩展通用 plugin surface,而不是把某个插件名字写进
run_agent.py、cli.py、gateway/run.py。 -
Dashboard chat 不要重复造。 主聊天在 TUI,Dashboard 嵌入 TUI。React 只做辅助结构化界面。
-
跨平台代码少用 POSIX 假设。 优先用
pathlib、tempfile、psutil等跨平台方案,避免/tmp、os.kill(pid, 0)、fcntl、termios等直接扩散。
7. 代码地图速查
| 主题 | 入口文件 |
|---|---|
| Agent 主循环 | run_agent.py |
| 工具发现与调用 | model_tools.py, tools/registry.py |
| 工具集定义 | toolsets.py |
| CLI 主体 | cli.py |
| CLI argparse 入口 | hermes_cli/main.py |
| slash command 注册 | hermes_cli/commands.py |
| 配置默认值 | hermes_cli/config.py |
| Hermes home/profile 路径 | hermes_constants.py |
| 日志 | hermes_logging.py |
| 会话存储 | hermes_state.py |
| Gateway 主循环 | gateway/run.py |
| 平台适配器 | gateway/platforms/ |
| TUI 前端 | ui-tui/src/ |
| TUI Python 桥 | tui_gateway/server.py |
| Dashboard API | hermes_cli/web_server.py |
| Dashboard 前端 | web/src/ |
| Cron | cron/jobs.py, cron/scheduler.py |
| Skills | skills/, optional-skills/, agent/skill_commands.py |
| Plugins | hermes_cli/plugins.py, plugins/ |
| Model providers | plugins/model-providers/, providers/ |
| Memory | agent/memory_manager.py, agent/memory_provider.py, plugins/memory/ |
| Terminal backends | tools/environments/ |
| Tests | tests/ |
8. 建议的学习练习
练习 1:追踪 /model
目标:理解 slash command 到模型切换的完整链路。
- 从
hermes_cli/commands.py搜索CommandDef("model"。 - 在
cli.py搜索canonical == "model"。 - 继续追踪 provider/model 如何写入 session 或 config。
- 看 Gateway 中
/model是否复用相同 resolver。
练习 2:追踪一个工具
目标:理解 tool schema 与 handler。
- 在
tools/中选一个简单工具,搜索registry.register。 - 找到 schema、handler、check_fn。
- 在
toolsets.py找它属于哪个 toolset。 - 在
model_tools.py追踪调用时如何解析 args 与包装结果。
练习 3:追踪一次 TUI 输入
目标:理解 Ink 与 Python 后端边界。
- 从
ui-tui/src/app.tsx找 prompt submit。 - 看 JSON-RPC method 名称。
- 在
tui_gateway/server.py搜索对应 method。 - 追踪它如何进入
AIAgent,又如何把 delta/event 发回 UI。
练习 4:新增一个只读配置项
目标:熟悉配置默认值和读取路径。
- 在
DEFAULT_CONFIG中加一个 harmless key。 - 找 CLI 与 Gateway 是否都能看到。
- 写一个小测试验证 deep merge 行为。
- 确认不需要 bump
_config_version。
9. 一句话总结
学习 Hermes Agent 不要从所有文件平均扫起。先抓住三条主线:
- 对话主线:
run_agent.py->model_tools.py->tools/registry.py - 入口主线:
hermes_cli/main.py/cli.py/gateway/run.py/tui_gateway/server.py - 扩展主线:
toolsets.py/skills//plugins//hermes_cli/config.py
这三条线通了,后面的 Gateway、TUI、Cron、Memory、Provider、Dashboard 都是在同一个核心循环外面接能力。