跳到正文
Erik Lee
返回

Hermes Agent 核心精华代码 · 一周学习计划

编辑页面

这份计划面向已经会用 Hermes Agent、但想深入其 runtime 实现的 agent 工程师。目标不是读完整套代码,而是抓住最核心的机制:一次 turn 怎么跑起来、上下文太长怎么办、记忆怎么存、技能怎么加载、工具怎么调度、多模型怎么协作、以及用户怎么在中途插话。


目标读者

学习方法论

  1. 先读高层、再进细节:每天先读仓库里的 origin 文章(如果已有),再对照源码验证
  2. 带着问题读代码:不要通读文件,先定位关键函数,再顺着调用链往外扩
  3. 动手做小实验:改配置、打日志、跑单测、写最小复现,才算真懂
  4. 每天写 200 字笔记:把”我以为是”改成”代码里确实是”

前置准备

# 1. 确认源码位置
ls /Users/eriklee/code/agent/hermes-agent

# 2. 进入虚拟环境
source /Users/eriklee/code/agent/hermes-agent/.venv/bin/activate

# 3. 确认核心包可导入
python -c "import hermes_cli, agent; print('ok')"

# 4. 准备阅读工具:带行号的编辑器/IDE,以及 grep/fzf

整体源码地图

/Users/eriklee/code/agent/hermes-agent/
├── run_agent.py                 # AIAgent 主类、steer 状态、会话入口
├── agent/
│   ├── conversation_loop.py     # ~3,900 行的 turn 执行引擎
│   ├── turn_context.py          # 每 turn 启动前的上下文准备
│   ├── turn_finalizer.py        # turn 收尾、预算、持久化
│   ├── context_engine.py        # ContextEngine ABC
│   ├── context_compressor.py    # 内置压缩引擎 ContextCompressor
│   ├── conversation_compression.py  # 压缩编排与 session 轮转
│   ├── trajectory.py            # 轨迹保存
│   ├── memory_manager.py        # Memory 协调器
│   ├── memory_provider.py       # Memory Provider ABC
│   ├── prompt_caching.py        # Anthropic prompt caching 策略
│   ├── skill_commands.py        # /skill-name 命令发现与调用
│   ├── skill_utils.py           # skill 元数据工具
│   ├── skill_bundles.py         # 多 skill bundle
│   ├── tool_executor.py         # 工具并发/串行执行
│   ├── tool_dispatch_helpers.py # 并行性判断、工具结果封装
│   ├── moa_loop.py              # Mixture of Agents 运行时
│   ├── moa_trace.py             # MoA trace 持久化
│   └── prompt_builder.py        # system prompt / steer marker 构建
├── hermes_cli/
│   ├── moa_cmd.py               # hermes moa 命令
│   └── moa_config.py            # MoA preset 解析
├── tools/
│   ├── registry.py              # 工具自注册表
│   ├── skills_tool.py           # skills_list / skill_view
│   ├── skills_hub.py            # skill hub 状态与 source adapter
│   ├── memory_tool.py           # 内置 MEMORY.md/USER.md 工具
│   └── session_search_tool.py   # 跨会话 FTS5 搜索
└── gateway/                     # 消息网关(可选,Week 2 内容)

Day 1 · Agent 主循环与 Turn 生命周期

核心问题:一次 user turn 从发消息到返回结果,完整链路是什么?

必读源码

文件关键符号说明
run_agent.py:403AIAgent主类,持有状态与配置
run_agent.pyrun_conversation()入口转发
agent/conversation_loop.py:518run_conversation()turn 执行引擎主体
agent/conversation_loop.py:277_restore_or_build_system_prompt()system prompt 重建
agent/turn_context.py:119build_turn_context()turn 启动前准备
agent/turn_context.py:93TurnContext本 turn 上下文数据类
agent/turn_finalizer.py:30finalize_turn()turn 收尾

学习步骤

  1. run_agent.py 找到 AIAgent,看它持有哪些字段(_pending_steer、memory manager、context engine、tool registry)
  2. 顺着 run_conversation() 进入 agent/conversation_loop.py:518
  3. 画出一次 turn 的主干流程:
    • 准备 TurnContext
    • 构建/恢复 system prompt
    • LLM API 调用
    • tool calls 执行
    • 结果回注
    • finalize
  4. 关注 agent/conversation_loop.py 中处理续流(continuation)和 failover 的分支

动手任务

今日输出


Day 2 · 上下文压缩(Context Compression)

核心问题:对话变长后,Hermes 如何在不截断的情况下压缩历史?

必读源码

文件关键符号说明
agent/context_engine.py:32ContextEngine可插拔上下文引擎 ABC
agent/context_compressor.py:662ContextCompressor内置压缩引擎
agent/context_compressor.py:540_summarize_tool_result()工具结果预裁剪
agent/context_compressor.py:483_strip_historical_media()历史媒体剥离
agent/conversation_compression.py:435compress_context()压缩编排入口
agent/conversation_compression.py:154check_compression_model_feasibility()启动探测
agent/conversation_compression.py:371conversation_history_after_compression()压缩后历史重建
gateway/run.py:9404gateway 会话卫生层85% 阈值安全网

学习步骤

  1. agent/context_engine.py,理解 ContextEngine ABC 的 5 个生命周期方法
  2. agent/context_compressor.py:662,理解 4 阶段流水线:
    • 触发判断
    • 保护头尾
    • 中段摘要
    • 结果回写
  3. agent/conversation_compression.py:435,看压缩失败如何回退
  4. 对比仓库已有的 origin 文章 content/origin/2026-07-01-hermes-context-compression/index.md,验证自己的理解

动手任务

今日输出


Day 3 · Memory 管理

核心问题:Hermes 如何在多轮、多会话之间保持对用户和任务的记忆?

必读源码

文件关键符号说明
agent/memory_manager.py:353MemoryManagermemory 协调器
agent/memory_manager.py:336build_memory_context_block()把 memory 拼进 system prompt
agent/memory_manager.py:100inject_memory_provider_tools()注入 memory provider 工具
agent/memory_provider.py:43MemoryProviderprovider ABC
tools/memory_tool.py:113MemoryStore文件级 memory 存储
tools/memory_tool.py:959memory_tool()agent 可调用入口
tools/memory_tool.py:1036apply_memory_pending()批量写回磁盘
tools/session_search_tool.py:619session_search()跨会话 FTS5 搜索
tools/session_search_tool.py:499_discover()全文检索与去重

学习步骤

  1. agent/memory_provider.py:43,列出 provider 必须实现的 8 个方法
  2. agent/memory_manager.py:353,看它是如何强制”最多一个 external provider”的
  3. tools/memory_tool.py,理解 MEMORY.md / USER.md 的读取-修改-写回流程:
    • 启动时加载为只读快照进入 system prompt
    • 运行中通过 memory_tool(add/replace/remove) 修改
    • turn 结束时 apply_memory_pending() 批量写盘
  4. tools/session_search_tool.py,理解跨会话召回的三种模式

动手任务

今日输出


Day 4 · Skills 机制

核心问题:Skills 是怎么被发现、加载、渐进式披露的?

必读源码

文件关键符号说明
tools/skills_tool.py:689skills_list()列出所有可用 skill 元数据
tools/skills_tool.py:864skill_view()加载完整 skill 或指定引用文件
tools/skills_tool.py:482check_skills_requirements()前置依赖检查
agent/skill_commands.py:348scan_skill_commands()发现 /skill-name 命令
agent/skill_commands.py:58extract_user_instruction_from_skill_message()从 skill 消息还原用户指令
agent/skill_commands.py:517build_skill_invocation_message()构造 skill 调用消息
agent/skill_utils.py:123parse_frontmatter()YAML frontmatter 解析
agent/skill_utils.py:499get_all_skills_dirs()发现 skill 目录
agent/skill_bundles.py:195get_skill_bundles()多 skill bundle
tools/skills_hub.py:424SkillSourceskill hub source adapter ABC

学习步骤

  1. tools/skills_tool.py:689,理解 skills_list() 的三层渐进式披露:
    • Level 0:只返回元数据(~3k tokens)
    • Level 1:skill_view(name) 加载完整 SKILL.md
    • Level 2:skill_view(name, path) 加载指定 reference/script
  2. agent/skill_utils.py,看目录扫描如何支持多层路径(category/skill-name/SKILL.md
  3. agent/skill_commands.py,理解 /skill-name 命令如何被注册成用户消息
  4. tools/skills_hub.py:424,看 hub source adapter 如何支持 GitHub / ClawHub / LobeHub

动手任务

今日输出


Day 5 · Tool Dispatch 与执行

核心问题:一次 LLM 返回多个 tool call 时,Hermes 怎么决定串行还是并发?

必读源码

文件关键符号说明
agent/tool_executor.py:306execute_tool_calls_concurrent()并发批量执行
agent/tool_executor.py:965execute_tool_calls_sequential()串行执行
agent/tool_executor.py:54_budget_for_agent()工具结果预算
agent/tool_dispatch_helpers.py:104_should_parallelize_tool_batch()并行判定
agent/tool_dispatch_helpers.py:80_is_destructive_command()破坏性命令启发式
agent/tool_dispatch_helpers.py:167_paths_overlap()路径独立性检查
agent/tool_dispatch_helpers.py:361make_tool_result_message()构造 tool result 消息
tools/registry.py:208ToolRegistry工具自注册表
tools/registry.py:58discover_builtin_tools()自动发现工具模块

学习步骤

  1. tools/registry.py,理解工具如何自注册(模块导入时注册)
  2. agent/tool_dispatch_helpers.py:104,理解并行判定规则:
    • 是否包含破坏性命令
    • 路径是否重叠
    • provider/工具级别的并发限制
  3. agent/tool_executor.py:306:965,对比并发与串行两条路径
  4. 关注 make_tool_result_message()_maybe_wrap_untrusted() 对 delimiter injection 的防护

动手任务

今日输出


Day 6 · MoA 与 Prompt Caching

核心问题:Hermes 如何把 Mixture of Agents 做进 agent loop?prompt caching 又怎么省钱?

必读源码

文件关键符号说明
agent/moa_loop.py:571aggregate_moa_context()MoA 聚合逻辑
agent/moa_loop.py:336_run_references_parallel()reference 并行执行
agent/moa_loop.py:220_run_reference()单个 reference 调用
agent/moa_loop.py:1041MoAClientclient facade
agent/moa_trace.py:97save_moa_turn()trace 持久化
hermes_cli/moa_cmd.py:79cmd_moa()hermes moa CLI
hermes_cli/moa_config.py:151normalize_moa_config()配置规范化
hermes_cli/moa_config.py:11DEFAULT_MOA_PRESET_NAME默认 preset
agent/prompt_caching.py:84apply_anthropic_cache_control()cache 标记入口
agent/prompt_caching.py:15_apply_cache_marker()给消息加 cache_control

学习步骤

  1. hermes_cli/moa_config.py,理解 preset 结构:references + aggregator
  2. agent/moa_loop.py:571,看 aggregate_moa_context() 如何收集 reference 输出并构造 aggregator prompt
  3. agent/moa_loop.py:336,看失败隔离(单个 reference 挂了怎么办)
  4. agent/prompt_caching.py:84,理解 system_and_3 策略:
    • system prompt 1 个 breakpoint
    • 历史消息中最多 3 个 breakpoint
    • 为什么压缩前必须冻结 system prompt

动手任务

今日输出


Day 7 · Mid-turn Steering 与全局串讲

核心问题:用户如何在 agent 正在跑工具时插话?Hermes 如何保证 role alternation 不被破坏?

必读源码

文件关键符号说明
run_agent.py:2730steer()接收用户 steer
run_agent.py:2766_drain_pending_steer()取出并清空 pending steer
run_agent.py:3042_apply_pending_steer_to_tool_results()注入 tool results
agent/prompt_builder.py:595format_steer_marker()包装 steer marker
agent/prompt_builder.py:600STEER_CHANNEL_NOTEsystem prompt 中的 steer 说明
agent/conversation_loop.pypre-API-call drain四个 drain 点

学习步骤

  1. run_agent.py:2730,看 steer() 如何用 threading.Lock 保护 _pending_steer
  2. agent/prompt_builder.py:595,理解 marker 格式和 STEER_CHANNEL_NOTE
  3. agent/conversation_loop.py,找到 pre-API、tool 批次后、leftover 等 4 个 drain 点
  4. 对比仓库已有的 origin 文章 content/origin/2026-06-11-hermes-steer-deep-dive/index.md,验证 Codex vs Hermes 的路线差异

动手任务

今日输出


每日 Checklist

时间任务是否完成
每天开始前读对应 origin 文章(如有),建立预期
每天上午按模块读源码,定位关键函数
每天下午完成一个动手实验
每天结束前写笔记/画图,提交到本地 git

动手实验清单(整周)


关键源码索引表

主题核心文件关键类/函数
主循环run_agent.py, agent/conversation_loop.pyAIAgent, run_conversation(), TurnContext, finalize_turn()
上下文压缩agent/context_engine.py, agent/context_compressor.py, agent/conversation_compression.pyContextEngine, ContextCompressor, compress_context()
Memoryagent/memory_manager.py, agent/memory_provider.py, tools/memory_tool.py, tools/session_search_tool.pyMemoryManager, MemoryProvider, MemoryStore, session_search()
Skillstools/skills_tool.py, agent/skill_commands.py, agent/skill_utils.py, tools/skills_hub.pyskills_list(), skill_view(), scan_skill_commands(), SkillSource
Tool dispatchagent/tool_executor.py, agent/tool_dispatch_helpers.py, tools/registry.pyexecute_tool_calls_concurrent(), _should_parallelize_tool_batch(), ToolRegistry
MoAagent/moa_loop.py, agent/moa_trace.py, hermes_cli/moa_config.pyaggregate_moa_context(), MoAClient, normalize_moa_config()
Prompt cachingagent/prompt_caching.pyapply_anthropic_cache_control()
Steeringrun_agent.py, agent/prompt_builder.py, agent/conversation_loop.pysteer(), format_steer_marker(), STEER_CHANNEL_NOTE

延伸阅读(仓库内已沉淀)


一周后你应能回答的问题

  1. 一次 user turn 从输入到输出的完整数据流是什么?
  2. ContextCompressor 为什么选择保护头尾、压缩中段?
  3. MEMORY.md 的读取和写入在生命周期上有什么不同?
  4. skills_list()skill_view() 的 token 成本差异有多大?
  5. Hermes 用什么规则决定 tool batch 并行还是串行?
  6. MoA 在 Hermes 里为什么被设计成”一个 model”?
  7. prompt caching 最多几个 breakpoint?为什么压缩不能破坏 system prompt?
  8. steer() 为什么不新增一条 user message,而是追加到 tool result?

本计划基于 Hermes Agent 代码仓 2026-07 快照制定。源码行号可能随版本漂移,建议结合当前代码验证。


编辑页面
分享这篇文章:

下一篇
Hermes Agent 的 MoA:把多模型协作做成一个 model