Hermes源码拆解|L2 Agent 核心:run_agent.py
⚠️ 完整内容请点击末尾“阅读原文”,强烈建议阅读原文噢!
源码拆解 | Hermes Agent
📄 基本信息
Hermes Agent 核心 run_agent.py
12K 行单文件生产级 Agent,支持 4 套 API 协议、55+ 工具、多平台部署
🎯 一句话总结
run_agent.py 是 Hermes Agent 的心脏起搏器——所有用户消息最终都要进入 AIAgent.run_conversation(),它负责把 LLM 思考 → 工具调用 → 再思考 → 最终回复这个循环稳定地跑起来,同时处理超时、错误恢复、上下文压缩、用户中断、provider 切换等生产环境的一切边缘情况。
🧠 核心思想
01 IterationBudget:防止无限套娃的硬上限
父 Agent 初始化 90 轮迭代预算,每次调 LLM 前调用 consume() 消费一次,返回 False 就退出循环。子 Agent 有独立预算(默认 50),execute_code 等轻量工具执行完可 refund() 归还。用 threading.Lock 保护计数器,解决模型陷入死循环(搜索→摘要→发现缺字段→再搜索)导致 API 费用爆炸的问题。
02 并行工具调度:多灶台智能分单
判断一批工具调用能否并行:只读工具(read_file / web_search)最多 8 线程并行;文件写入工具检查路径冲突(/a/b/c.py 和 /a/b/d.py 可并行,/a/b/c.py 和 /a/ 冲突则串行);交互工具(clarify)绝对串行。用 ThreadPoolExecutor(max_workers=8),每个 worker 注册到 _tool_worker_threads 集合,interrupt() 能精确广播中断信号到所有在跑线程。以纯软件手段榨出 5–10 倍感知速度。
03 AIAgent.__init__:1100 行后厨初始化清单
一个 AIAgent 实例开工前要确认 60+ 个开关:API 协议自动路由(Anthropic / Codex / Bedrock / OpenAI 四协议自动识别)→ 客户端构建(OAuth、Prompt Caching、OpenRouter 归因)→ 55+ 工具加载 → 记忆系统(内建 MemoryStore + 可插拔 MemoryManager)→ 上下文压缩引擎(ContextCompressor 或 LCM 插件)→ Fallback chain → 用量计费。把模型选择从代码级决策降到配置级决策。
04 _build_system_prompt:Prompt Cache 优先设计
系统提示一次会话内永不变化,所有动态内容(记忆 prefetch、插件 context、ephemeral_prompt)一律注入 user message。为什么?Anthropic Prompt Cache 必须前缀一致才能复用,一旦系统提示每次都带新时间戳,缓存就废了,input token 成本飙升 4 倍。刻意做静/动分离:系统提示 = 静态内容(cache 到 _cached_system_prompt);user message = 动态内容。每月账单直接砍 50% 以上。
05 interrupt + /steer:两种打断主厨的方式
interrupt = 拍桌喊停:立即中止所有 worker 线程,用新消息重启循环,打破 role 交替。steer = 递小纸条:当前工具批次结束后,把文本追加到最后一个 tool result 的末尾,不影响正在执行的工具。interrupt 会扫描所有 worker 线程逐个发中断信号;steer 用 Lock 保护 _pending_steer 字段,支持多次调用拼接。把 Agent 从单次请求-响应升级为持续对话流。
06 _interruptible_api_call:叫外卖 + 定时炸弹
调 LLM API 时启动独立后台线程发请求,主线程同时监控打断和超时。每 30 秒心跳通知 Gateway;Stale-call detector 根据上下文大小动态算 _stale_timeout,超时强杀连接。每个请求用独立 request_client,kill 连接不影响其他并发请求。解决 Cloudflare 524、httpx 僵尸 socket、OpenRouter SSE 断流等生产常见问题,把 provider 可用性从 99.5% 拉到 99.95%。
07 错误分类 + Fallback Chain:十一层应急预案
不是简单重试 3 次就 500,而是按错误类型走不同恢复路径:rate_limit→jittered backoff + 可选 fallback;capacity/overloaded/billing→立即 fallback;context_overflow→触发 _compress_context 后重试;auth→走凭证池轮转。Fallback 链支持多 provider 依次尝试,每项独立配置 base_url 和 api_key。每轮开始 _restore_primary_runtime(),fallback 只在当前轮生效。把 99% 可用的模型叠成 99.99% 可用的 Agent。
08 工具执行双路径:并行 vs 串行
并发路径的精妙之处:① 预执行阶段为 write_file / patch 预先做 checkpoint snapshot,为破坏性 terminal 命令也做 checkpoint;② 线程池 8 个 worker 同时跑;③ 每 5 秒轮询 interrupt,发现中断就 cancel 未启动的 future;④ 按原 index 填回 results[] 数组,保证 API 看到的 tool result 顺序与 tool call 顺序一致;⑤ 后处理包括大结果持久化、单轮 token 预算控制、steer 文本挂载。
09 上下文压缩:厨房整理老订单
当会话历史塞到 context window 的 50% 以上时自动触发:先给模型机会 flush_memories 再存点记忆 → 通知外部 provider 做抽取 → context_compressor.compress(cheap pre-pass 裁剪工具输出 + 辅助 LLM 生成 Resolved/Pending/Remaining Work 三段摘要)→ 中间 N 条替换为一条 user 消息 → 重建系统提示 → SQLite 里会话切成两段。压缩 2 次后主动警告用户 accuracy may degrade,让单会话支撑数百轮从不可能变成默认。
🔬 技术方法概述
如果把 run_agent.py 类比为一家餐厅:run_conversation(user_message) 是大门/点餐台,负责安检(净化 surrogate 字符、剥离泄漏的 memory-context)、迎宾(恢复主模型)、备料(pre-flight 上下文压缩、插件 pre_llm_call hook)。进入 while 主循环后,组装 api_messages(系统提示 + 历史 + 当前 user,含注入的 ext_prefetch_cache + plugin_context)→ 打 Anthropic cache_control 标记 → 调用 _interruptible_api_call(chat_completions / anthropic / codex 三选一,独立 worker thread + 超时杀连接)→ 返回 assistant_message(含 tool_calls)→ 验证工具名(自动修复拼写)、验证 JSON 参数(自动修复截断)→ _execute_tool_calls(并行判定 + 并发 8 线程 / 顺序执行)→ 结果追加到 messages → 返回循环顶部。无 tool_calls 时退出循环,保存 trajectory + session DB,触发 memory/skill nudge(后台线程),返回 final_response。异常处理走 error_classifier → failover_chain → retry;上下文饱和走 _compress_context。
📊 关键数据
12K
行单文件
4套
API 协议
55+
内置工具
90轮
迭代预算
8线程
并行工具
💡 产品经理视角
🔹 Iteration Budget 必须设:给 Agent 设置硬上限迭代预算不是为了限制能力,而是为了:① 成本可预测(单次对话最贵 = budget × 平均每轮 token);② 用户耐心管理(10 分钟没完成的任务自动总结我尽力了);③ 监控信号(预算耗尽率>20%意味着模型能力或提示词需优化)。
🔹 Checkpoint Before Destructive Op:任何修改文件系统或发送外部请求的工具调用前都应该 snapshot。这是让 Agent 能后悔的关键——不仅用户觉得安全,模型自己也能走试探式路径。
🔹 Prompt Cache 作为设计第一原则:把静态/动态分离作为 Agent 产品设计的第一原则:系统提示 = 静态身份 + 工具列表 + 规范;user message = 所有上下文(包括检索结果)。这样所有长对话场景都能吃到缓存红利。
🔹 Fallback Chain 是 SLA 生命线:准备 3 个 provider 按便宜优先排序。成本低 40% 且可用性高 0.5%。C 端产品 Claude 挂了自动切 GPT-5 用户感知不到;合规场景国内请求切 DashScope,海外切 Anthropic。
🔹 Turn-exit Diagnostic Logging:每一轮结束强制记录 _turn_exit_reason。用户投诉 Agent 卡住了时,日志能直接告诉你是 max_iterations_reached 还是 interrupted_by_user。
🔹 自我改进闭环:用户对话 → 每 N 轮记忆 Nudge 你学到了什么 → 每 M 次工具迭代技能 Nudge 这个流程要不要变成技能 → 形成的技能下次类似任务自动加载。这是 Hermes 区别于 LangChain / AutoGen 的关键——Agent 不是一次性工具,而是越用越聪明的系统。
🔹 透明诚实是高级用户粘性的核心:压缩 2 次后主动警告用户 accuracy may degrade;Turn-exit reason 永远记录;错误信息给出具体恢复建议;不隐瞒我重试了 3 次的事实。这种不装的产品气质,是高级用户粘性的核心。
🔹 上帝类 vs 模块化:不要为了架构好看而过度拆分:单次 run_conversation 需要访问 60+ 字段,分成多个类会让调用图爆炸;错误恢复路径跨越多层逻辑,上帝类让跨层跳转自然;Gateway 缓存 Agent 实例保留 prompt cache,上帝类里的状态天然成为缓存内容。代价是难以测试、新人上手慢,但生产压力下这是理性选择。
🔹 基于 run_agent.py 的产品方向:① 企业内 Agent 平台:Hermes 做底层 + 自研 fine-tuned 模型 + 内部工具(JIRA / Confluence);② 垂直 Coding Agent:保留并行工具 + checkpoint + 压缩,替换为某编程语言专用工具;③ Agent as API:把 Hermes 扩展为 MCP 服务端,其他产品把 Hermes 当高级 LLM 调用。
🔗 延伸阅读
1. Hermes Agent 官方仓库(NousResearch, GitHub)
↳ 本文分析的完整源码,12K 行单文件生产级 Agent 实现
2. context_compressor.py(配套模块)
↳ 上下文压缩策略:头尾保护 + 中间摘要,支持 LCM 插件切换
3. error_classifier.py(配套模块)
↳ 错误分类与恢复路径:FailoverReason 枚举 + 11 层恢复策略
4. prompt_caching.py(配套模块)
↳ Prompt Cache 策略:静/动分离设计, Anthropic cache_control 标记
5. anthropic_adapter.py(配套模块)
↳ Anthropic 协议适配:支持 prompt caching、thinking blocks
6. codex_responses_adapter.py(配套模块)
↳ Codex Responses 协议适配:GPT-5.x 系列模型专用
源码拆解 | 2026-04-24 | by 赛博阁员张居正
⚠️ 完整内容请点击左下方“阅读原文”,强烈建议阅读原文噢!
夜雨聆风