Skip to content

Entrée et flux de démarrage

源码版本v2026.7.20

Responsabilité

Transformer « une commande hermes / un message de la passerelle (gateway) » en « un agent qui tourne en mémoire ». La couche d'entrée est délibérément fine — run_agent.py ne fait que transférer et assembler le CLI, la véritable injection de dépendances a lieu dans agent_init.py. Ainsi la passerelle, le TUI, le CLI, ACP et cron partagent le même chemin d'initialisation.

Mot de conception

Couche d'entrée fine, assemblage centralisé : tout pour que chaque chemin d'appel n'ait qu'un seul apprentissage. Si AIAgent.__init__ résolvait lui-même le provider, fusionnait l'extra_body et calculait le seuil de compression (compression), alors la passerelle, le TUI et le cron devraient chacun répliquer cette logique pour lancer un agent — une modif entraîne des modif' partout. En réduisant __init__ à un simple forwarder, les décisions réelles convergent dans une seule fonction, init_agent ; brancher un nouveau frontend (par exemple un futur démon MCP) revient à construire un AIAgent pour obtenir la pleine capacité, sans rien connaître des détails d'assemblage.

hermes_bootstrap.py placé tout en tête suit la même logique : la correction UTF-8 sous Windows est une réparation environnementale du « plus tôt le mieux », impossible à rattraper une fois les sous-processus déjà spawnés ; il est donc isolé dans un module que tous les points d'entrée importent à leur toute première ligne.

Fichiers clés

Flux de données

  1. L'utilisateur lance hermes (CLI) ou la passerelle démarre via main() (run_agent.py:22965).
  2. Le processus passe par hermes_bootstrap.py pour corriger chemins et environnement, puis entre dans CLI main:6434 ou la boucle de la passerelle.
  3. Construction d'une instance AIAgent:400, dont __init__ forward vers init_agent:276.
  4. init_agent résout provider, toolsets, seuils de compression, extra_body des providers personnalisés, etc., et assemble un objet agent exécutable.
  5. La passerelle, dans GatewayRunner (gateway/run.py), détient cet agent et alimente chaque message entrant à run_conversation:588.

Le corps de AIAgent.__init__ est la définition littérale de « forwarder » — les paramètres du constructeur sont passés tels quels à init_agent, sans aucun parsing :

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,
    ...
)

Et le hermes_bootstrap.py, premier à toucher le processus, ne fait de rebond d'encodage que sous Windows ; POSIX passe à travers, et la logique est très courte :

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 plutôt que = est intentionnel : l'utilisateur peut placer PYTHONUTF8=0 dans l'environnement à l'avance pour opt-out, sans se faire écraser.

Limites et échecs

  • Entrée lancée directement : quelqu'un contourne hermes_bootstrap et lance python -m gateway.run sous Windows — stdio reste cp1252, les sorties non-ASCII lèvent un UnicodeEncodeError. La position du bootstrap est une convention, pas une contrainte ; la doc demande à tous les points d'entrée de l'importer en première ligne.
  • AIAgent.__init__ qui lève avant init_agent : comme le corps de __init__ n'est qu'un import + appel, tout échec vient de l'intérieur de init_agent, et la stack frame donne l'impression d'un « décalage ». En debug, la dernière frame du traceback est init_agent ; ne pas se laisser induire en erreur par le commentaire de __init__ qui suggère « le forwarder lui-même a un bug ».
  • L'import dans init_agent est paresseux : from agent.agent_init import init_agent est écrit dans le corps de __init__ plutôt qu'en tête de module, pour éviter un import circulaire — agent_init importe en retour run_agent. Le prix : un import supplémentaire à la première construction de AIAgent (de l'ordre de la milliseconde), ensuite servi par le cache module.
  • reconfigure de hermes_bootstrap peut échouer : si stdout a été remplacé par un BytesIO (scénario de test) ou est déjà fermé, reconfigure lève OSError / ValueError ; le code catch et saute silencieusement. La branche variable d'environnement reste active, donc les sous-processus ne sont pas impactés.

Résumé

La couche d'entrée est « transfert + assemblage », sans logique métier. AIAgent est une coquille fine, init_agent est le véritable centre d'assemblage. Une fois cette chaîne comprise, la boucle principale de l'agent est simplement « ce qui se passe quand on alimente un message ».

Site d'apprentissage communautaire non officiel. Basé sur le code source de NousResearch/hermes-agent (licence MIT).