Entrée et flux de démarrage
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
main de la passerelle:22965— entrée du processus passerelle, fonctionmain()classe AIAgent:400-497— classe d'entrée légère,__init__ne fait que stocker les paramètresAIAgent.__init__ forwarder:497-560— commentaire explicite : « Forwarder — see agent.agent_init.init_agent »méthode run_conversation:6350— méthode d'instanceAIAgent.run_conversation, appellerun_conversation au niveau module:588CLI main:6434— entrée CLIinit_agent:276-400— fonction principale d'injection de dépendances_resolve_compression_threshold:93-127— exemple de parsing de config au démarragehermes_bootstrap.py— amorce environnement/chemins la plus précoce (racine du dépôt)
Flux de données
- L'utilisateur lance
hermes(CLI) ou la passerelle démarre viamain()(run_agent.py:22965). - Le processus passe par
hermes_bootstrap.pypour corriger chemins et environnement, puis entre dansCLI main:6434ou la boucle de la passerelle. - Construction d'une instance
AIAgent:400, dont__init__forward versinit_agent:276. init_agentrésout provider, toolsets, seuils de compression,extra_bodydes providers personnalisés, etc., et assemble un objet agent exécutable.- 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 :
"""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 :
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_bootstrapet lancepython -m gateway.runsous Windows — stdio reste cp1252, les sorties non-ASCII lèvent unUnicodeEncodeError. 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 avantinit_agent: comme le corps de__init__n'est qu'un import + appel, tout échec vient de l'intérieur deinit_agent, et la stack frame donne l'impression d'un « décalage ». En debug, la dernière frame du traceback estinit_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_agentest paresseux :from agent.agent_init import init_agentest écrit dans le corps de__init__plutôt qu'en tête de module, pour éviter un import circulaire —agent_initimporte en retourrun_agent. Le prix : un import supplémentaire à la première construction deAIAgent(de l'ordre de la milliseconde), ensuite servi par le cache module. reconfiguredehermes_bootstrappeut échouer : si stdout a été remplacé par unBytesIO(scénario de test) ou est déjà fermé,reconfigurelèveOSError/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 ».