How Hermes Starts a Conversation: Full Walkthrough of the Three‑Layer cli.py → AIAgent Init
The article dissects Hermes' three‑layer startup chain—CLI (cli.py), AIAgent facade, and init_agent engine—explaining each layer's responsibilities, lazy imports, session handling, and common pitfalls to help developers understand how a single dialogue is brought to life.
The article explains the three‑layer startup chain of Hermes: the CLI layer (cli.py), the Agent layer (AIAgent), and the initialization engine (agent_init.py). Separating concerns avoids the pitfalls of monolithic main.py implementations.
Why three layers?
Single‑file designs mix UI, configuration, and business logic, making testing and platform changes hard. Hermes assigns each layer a single responsibility: the CLI handles TUI rendering, key bindings, and config loading; the Agent layer forwards parameters via a Facade; init_agent assembles the runtime state.
CLI layer details
At the bottom of cli.py is fire.Fire(main), which maps CLI arguments to the main() function. main() performs three core tasks:
Configuration loading via load_cli_config(). The search order is ~/.hermes/config.yaml → ./cli-config.yaml → built‑in defaults, with ${ENV_VAR} expansion. Terminal‑related settings are bridged into os.environ (e.g., TERMINAL_ENV, BROWSER_INACTIVITY_TIMEOUT), allowing any thread to read them without passing a config object.
TUI construction using prompt_toolkit instead of a simple input(). This enables simultaneous streaming output and input, multi‑line editing, command history, autocomplete, and resize‑aware rendering.
Slash‑command dispatch (e.g., /model, /tools, /clear) intercepted at the CLI layer, never reaching the Agent, ensuring clean separation of concerns.
Agent layer
run_agent.pydefines AIAgent.__init__, which accepts over 60 parameters but contains almost no logic. It follows the Facade pattern and lazily imports init_agent to perform real initialization.
Design rationale
Testability : init_agent(agent, ...) is a plain function that can be mocked directly.
Replaceability : swapping the initialization implementation only requires changing init_agent, leaving the AIAgent API unchanged.
Startup performance : lazy imports avoid heavy module loading at import time.
Initialization engine (init_agent)
The init_agent() function performs four major steps:
API mode detection based on provider or base_url, selecting the appropriate transport (e.g., anthropic_messages, bedrock_converse, codex_responses, or default chat_completions).
Session ID generation and dual write to os.environ and a ContextVar. This supports both CLI mode (simple env var read) and Gateway mode (concurrent sessions isolated via ContextVar).
Memory lazy loading : load_from_disk() is wrapped in try/except pass; failures are non‑fatal, with a default character limit of 2200.
Tool‑set assembly : each toolset declares dependencies (e.g., docker_toolset requires TERMINAL_ENV=docker). Missing dependencies are silently skipped.
Lazy import of the OpenAI SDK
In run_agent.py, the OpenAI SDK is imported lazily through a proxy object. The comment explains that a top‑level from openai import OpenAI costs ~240 ms. The proxy delays the import until the first call, making commands like hermes --list-tools start instantly and keeping unit‑test patches effective.
Full startup sequence
fire.Fire(main)parses CLI arguments. load_cli_config() reads configuration and bridges env vars. HermesCLI.__init__() builds the prompt_toolkit TUI. HermesCLI.run() displays the banner and enters the REPL loop.
User inputs the first message. AIAgent.__init__() forwards parameters via the Facade. init_agent(self, ...) assembles state: API mode detection → session ID dual write → Memory lazy load → tool‑set assembly → callback registration. run_conversation(message) starts the first dialogue round.
Each step has a clear boundary: the CLI layer never touches business logic, the Agent layer only forwards parameters, and init_agent focuses on state assembly.
Common pitfalls
Pitfall 1: Initialization failures are often swallowed by try/except pass, so Memory may stay inactive without raising errors. Check ~/.hermes/logs/agent.log for warning messages.
Pitfall 2: In Gateway mode, multiple concurrent sessions can overwrite HERMES_SESSION_ID in os.environ. Use the ContextVar or pass the session ID explicitly.
Pitfall 3: Heavy imports inside a toolset (e.g., import torch) incur import cost even if the toolset is disabled. Place such imports inside functions for lazy loading.
Pitfall 4: CI environments may ignore a local cli-config.yaml because the search order prefers ~/.hermes/config.yaml, leading to divergent behavior between CI and local runs.
Summary
The CLI layer handles human interaction without holding business state; AIAgent acts as a thin API Facade forwarding over 60 parameters; init_agent() performs the real state assembly. Lazy imports make simple commands instantaneous and keep patch‑based tests functional. Dual session‑ID writes reconcile CLI and Gateway modes, and all optional components degrade gracefully, ensuring core functionality never crashes.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
James' Growth Diary
I am James, focusing on AI Agent learning and growth. I continuously update two series: “AI Agent Mastery Path,” which systematically outlines core theories and practices of agents, and “Claude Code Design Philosophy,” which deeply analyzes the design thinking behind top AI tools. Helping you build a solid foundation in the AI era.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
