入口与启动流程
源码版本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 它。
关键文件
gateway main:22965— 网关进程入口main()AIAgent 类:400-497— 瘦入口类,__init__仅存参AIAgent.__init__ 转发:497-560— 注释明确写 "Forwarder — see agent.agent_init.init_agent"run_conversation 方法:6350—AIAgent.run_conversation实例方法,调用模块级 run_conversation:588CLI main:6434— CLI 入口init_agent:276-400— 依赖注入主函数_resolve_compression_threshold:93-127— 启动期配置解析示例hermes_bootstrap.py— 最早期的环境/路径引导(仓库根)
数据流
- 用户执行
hermes(CLI)或网关main()启动(run_agent.py:22965)。 - 进程经
hermes_bootstrap.py做路径/环境修正,进入CLI main:6434或网关循环。 - 构造
AIAgent:400实例,其__init__转发到init_agent:276。 init_agent解析 provider、toolsets、压缩阈值、自定义 provider extra_body 等,装配出可运行的 agent 对象。- 网关在
gateway/run.py的GatewayRunner里持有该 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反过来要 importrun_agent。代价是首次构造AIAgent多一次 import 开销(~毫秒级),之后走模块缓存。hermes_bootstrap的 reconfigure 可能失败:如果 stdout 被替换成BytesIO(测试场景)或已经关闭,reconfigure会抛OSError/ValueError,代码 catch 后静默跳过。环境变量那条路径仍然生效,所以子进程不受影响。
小结
入口层是「转发 + 装配」,不持业务逻辑。AIAgent 是瘦壳,init_agent 才是装配中心。理解这条链路后,后面的Agent 主循环就是「被喂消息后做什么」。