Skip to content

入口与启动流程

源码版本v2026.7.20

职责

把「一条 hermes 命令 / 一个网关消息」变成「内存里跑起来的 agent」。入口层刻意做得很薄——run_agent.py 只负责转发和 CLI 装配,真正的依赖注入在 agent_init.py。这样网关、TUI、CLI、ACP、cron 能共用同一套初始化路径。

设计动机

入口层薄、装配集中,是为了让所有调用路径只学一套。如果 AIAgent.__init__ 自己解析 provider、合并 extra_body、算压缩阈值,那网关、TUI、cron 想跑 agent 就得各自复刻这套逻辑,改一处要改多处。把 __init__ 退化成转发器,真正决策收敛在 init_agent 一个函数里,新接入一个前端(比如未来的 MCP 守护进程)只要构造 AIAgent 就拿到完整能力,不必知道装配细节。

hermes_bootstrap.py 放在最前面也是同样的思路:Windows UTF-8 是「越早越好」的环境修正,等到子进程派生以后再补救就来不及,所以它独立成模块,所有入口点第一行就 import 它。

关键文件

数据流

  1. 用户执行 hermes(CLI)或网关 main() 启动(run_agent.py:22965)。
  2. 进程经 hermes_bootstrap.py 做路径/环境修正,进入 CLI main:6434 或网关循环。
  3. 构造 AIAgent:400 实例,其 __init__ 转发到 init_agent:276
  4. init_agent 解析 provider、toolsets、压缩阈值、自定义 provider extra_body 等,装配出可运行的 agent 对象。
  5. 网关在 gateway/run.pyGatewayRunner 里持有该 agent,把每条入站消息喂给 run_conversation:588

AIAgent.__init__ 的函数体就是「转发器」三个字的字面意思——构造参数一个不落地透传给 init_agent,自己不做任何解析:

python
"""Forwarder — see ``agent.agent_init.init_agent``."""
from agent.agent_init import init_agent
init_agent(
    self,
    base_url=base_url,
    api_key=api_key,
    provider=provider,
    api_mode=api_mode,
    ...
)

而最早接触进程的 hermes_bootstrap.py 只在 Windows 上做编码兜底,POSIX 直接放行,逻辑很短:

python
if not _IS_WINDOWS:
    return False
if _bootstrap_applied:
    return False
os.environ.setdefault("PYTHONUTF8", "1")
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
for stream_name in ("stdout", "stderr"):
    stream = getattr(sys, stream_name, None)
    ...
    reconfigure(encoding="utf-8", errors="replace")

setdefault 而不是 = 是故意的:用户可以提前把 PYTHONUTF8=0 放进环境变量来主动 opt-out,不会被覆盖。

边界与失败

  • 入口被直接构造:有人绕过 hermes_bootstrap,直接 python -m gateway.run 在 Windows 上起进程,stdio 仍是 cp1252,非 ASCII 输出会 UnicodeEncodeError。bootstrap 的位置是约定,不是强制,文档要求所有入口点第一行 import。
  • AIAgent.__init__init_agent 之前抛异常:由于 __init__ 函数体只是 import + 调用,任何失败都来自 init_agent 内部,栈帧会显得「错位」。调试时看 traceback 的最后一帧是 init_agent,不要被 __init__ 的注释误导成「转发器本身有 bug」。
  • init_agent 的导入是惰性的:from agent.agent_init import init_agent 写在 __init__ 函数体里而不是模块顶部,是为了避免循环导入——agent_init 反过来要 import run_agent。代价是首次构造 AIAgent 多一次 import 开销(~毫秒级),之后走模块缓存。
  • hermes_bootstrap 的 reconfigure 可能失败:如果 stdout 被替换成 BytesIO(测试场景)或已经关闭,reconfigure 会抛 OSError/ValueError,代码 catch 后静默跳过。环境变量那条路径仍然生效,所以子进程不受影响。

小结

入口层是「转发 + 装配」,不持业务逻辑。AIAgent 是瘦壳,init_agent 才是装配中心。理解这条链路后,后面的Agent 主循环就是「被喂消息后做什么」。

非官方社区学习站,内容以 MIT 许可的 NousResearch/hermes-agent 源码为依据。