Entry & Boot Flow
Responsibility
Turn "a hermes command / a gateway message" into "an agent running in memory". The entry layer is deliberately thin — run_agent.py only forwards and assembles the CLI; the real dependency injection lives in agent_init.py. This way the gateway, TUI, CLI, ACP, and cron all share the same initialization path.
Design Motivation
The entry layer is kept thin and assembly centralized so that every caller path only has to learn one thing. If AIAgent.__init__ itself parsed providers, merged extra_body, and computed compression thresholds, then the gateway, TUI, and cron would each have to replicate that logic — one change would need to land in many places. By reducing __init__ to a forwarder and concentrating real decisions in the single init_agent function, a new frontend (say a future MCP daemon) just constructs AIAgent to get the full feature set without knowing any assembly details.
hermes_bootstrap.py is placed first for the same reason: Windows UTF-8 is an "the earlier the better" environment fix. Once a child process is forked it's too late to patch, so it stands alone as a module that every entry point imports on its very first line.
Key Files
gateway main:22965— gateway process entrymain()AIAgent class:400-497— thin entry class;__init__only stores parametersAIAgent.__init__ forwarder:497-560— comment explicitly says "Forwarder — see agent.agent_init.init_agent"run_conversation method:6350—AIAgent.run_conversationinstance method, callsmodule-level run_conversation:588CLI main:6434— CLI entryinit_agent:276-400— dependency injection main function_resolve_compression_threshold:93-127— startup-time config parsing examplehermes_bootstrap.py— earliest environment/path bootstrap (repo root)
Data Flow
- The user runs
hermes(CLI) or the gatewaymain()starts (run_agent.py:22965). - The process goes through
hermes_bootstrap.pyfor path/environment fixes, then entersCLI main:6434or the gateway loop. - An
AIAgent:400instance is constructed; its__init__forwards toinit_agent:276. init_agentresolves providers, toolsets, compression thresholds, custom provider extra_body, etc., assembling a runnable agent object.- The gateway holds this agent inside
GatewayRunneringateway/run.pyand feeds each inbound message torun_conversation:588.
The body of AIAgent.__init__ is the literal embodiment of "forwarder" — every constructor argument is passed through to init_agent unchanged, with no parsing of its own:
"""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,
...
)And hermes_bootstrap.py, the first thing the process touches, only does encoding fallback on Windows — POSIX passes straight through. The logic is short:
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 instead of = is deliberate: a user can pre-set PYTHONUTF8=0 in the environment to opt out, and it won't be overwritten.
Boundaries and Failure
- Entry constructed directly: If someone bypasses
hermes_bootstrapand runspython -m gateway.rundirectly on Windows, stdio is still cp1252 and non-ASCII output raisesUnicodeEncodeError. The bootstrap's position is convention, not enforcement — docs require every entry point to import it on the first line. AIAgent.__init__throws beforeinit_agent: Since__init__'s body is just an import + a call, any failure originates insideinit_agent, but the stack frame looks "misaligned". When debugging, the last frame on the traceback isinit_agent; don't be misled by the__init__comment into thinking "the forwarder itself has a bug".init_agentimport is lazy:from agent.agent_init import init_agentlives inside__init__'s body rather than at module top, to avoid a circular import —agent_initin turn importsrun_agent. The cost is one extra import (~ms-level) the first time anAIAgentis constructed; after that the module cache kicks in.hermes_bootstrap's reconfigure can fail: If stdout has been swapped for aBytesIO(test scenarios) or is already closed,reconfigureraisesOSError/ValueError; the code catches and silently skips. The environment-variable path still applies, so child processes are unaffected.
Summary
The entry layer is "forward + assemble" and holds no business logic. AIAgent is a thin shell; init_agent is the assembly center. Once you understand this chain, the Agent Main Loop is just "what happens when a message is fed in".