Skip to content

Vue d'architecture

源码版本v2026.7.20

Hermes Agent est un agent de bureau open source (MIT) de Nous Research, qui se décrit comme « un agent persistant qui grandit avec vous » : un message arrive depuis Telegram/Discord/Slack/etc, la boucle principale appelle le modèle + les outils (tools) pour accomplir la tâche, et l'expérience est distillée en mémoire et compétences pour être réutilisée ensuite.

Vue en couches

Chaque couche en une phrase

  • Couche UI : TUI / CLI / Web / ACP (protocole éditeur) — uniquement des points d'entrée, sans logique métier.
  • Passerelle : gateway/run.py unifie l'accès ; les plateformes sont des adaptateurs (adapters) ; session/livraison séparées ; streams et hooks enfichables.
  • Boucle principale : run_conversation orchestre « appel modèle → appel outil → remplissage du résultat → décrément du budget (budget) », avec repli (fallback) en cas d'échec.
  • Capacités : Les outils sont des fonctions atomiques (tools/registry.py) ; les Skills sont des flux (stream) évolutifs d'ordre supérieur (réécrits par la boucle d'apprentissage) ; Provider/MCP/Plugins fournissent modèles et capacités externes.
  • Planification : Cron pilote les tâches planifiées, Subagent délègue en isolation (coût de contexte (context) nul), 6 backends de bac à sable isolent l'exécution.
  • Boucle d'apprentissage : Distille l'expérience des conversations en compétences et un modèle d'utilisateur — ce qui le rend « meilleur à l'usage ».

Top-down : signature représentative de chaque couche

Pour comprendre la stratification d'Hermes, le plus rapide est de regarder quelle « signature » chaque couche expose dans le code.

Couche UI → redirecteur vers la boucle principale. Toutes les coquilles appellent AIAgent.run_conversation, mais ce n'est qu'un forward (run_agent.py:6350):

python
def run_conversation(self, user_message, system_message=None,
                     conversation_history=None, ..., moa_config=None) -> Dict[str, Any]:
    """Forwarder — see ``agent.conversation_loop.run_conversation``."""
    from agent.conversation_loop import run_conversation
    token = set_conversation_context(self._conversation_root_id())
    with scoped_runtime_main({}):
        return run_conversation(self, user_message, ...)

L'ID de conversation est propagé via un ContextVar — tous les appels LLM à l'intérieur de la boucle principale (compression (compression), vision, slot MoA, fork de background_review) sont automatiquement étiquetés.

Passerelle → directeur des adaptateurs de plateforme. GatewayRunner assemble les capacités d'autorisation / kanban / slash commands via des mixins (gateway/run.py:22392), l'entrée start_gateway(replace=True) tue d'abord l'ancienne instance — un filet pour les redémarrages systemd où l'ancien processus n'a pas fini de sortir :

python
class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, GatewaySlashCommandsMixin):
    """Main gateway controller. Manages the lifecycle of all platform adapters
    and routes messages to/from the agent."""

Boucle principale → là où le travail se fait vraiment. run_conversation est dans agent/conversation_loop.py, orchestre « appel modèle → appel outil → remplissage du résultat → décrément du budget », repli en cas d'échec ; signature ci-dessus.

Capacités → enregistrement des outils. Chaque tools/*.py appelle registry.register à l'import du module (tools/registry.py:365), override=True est la seule façon pour un plugin (plugin) de remplacer un outil intégré :

python
def register(self, name, toolset, schema, handler, check_fn=None,
             requires_env=None, is_async=False, description="", emoji="",
             max_result_size_chars=None, dynamic_schema_overrides=None, override=False):
    """Register a tool.  Called at module-import time by each tool file.
    ``override=True`` is an explicit opt-in for plugins ... Without it,
    registrations that would shadow an existing tool are rejected."""

Un plugin qui veut remplacer un outil intégré doit explicitement passer override=True et passer la config allow_tool_override — pour empêcher qu'un plugin remplace discrètement l'outil browser. La surcharge homonyme d'un outil MCP passe par un canal séparé.

Planification → le tick de cron. tick (cron/scheduler.py:3888) est un scan unique protégé par un verrou fichier ; la passerelle l'appelle depuis un thread en arrière-plan toutes les 60 secondes ; le paramètre adapters permet aux tâches cron de réutiliser les connexions plateforme déjà établies par la passerelle, sans se reconnecter elles-mêmes.

Boucle d'apprentissage → agrégation du graphe d'apprentissage. build_learning_graph (agent/learning_graph.py:248-328) assemble en nœuds/arêtes les compétences et mémoires apprises (non intégrées). Il appelle d'abord build_skill_nodes(_skill_roots()) pour scanner les deux racines base et profile, puis filtre source != "base" et created_by == "agent" ou use_count > 0 pour identifier les compétences « apprises » — les compétences intégrées n'entrent pas dans le graphe d'apprentissage, pour éviter que le bruit couvre le signal évolutif.

Motif de stratification

Pourquoi cette stratification ? En une phrase : que les couches modifiables ne polluent pas les couches immuables.

  • La couche UI change de coquille fréquemment (TUI / Web / protocole IDE ont chacun leur rythme d'itération), on ne peut pas laisser un changement de coquille imposer des modifications métier ; la coquille ne fait que suspendre des callbacks (step_callback / event_callback), elle n'entre pas dans run_conversation.
  • Intégrer une nouvelle plateforme dans la passerelle est la norme (QQbot, WeChat entreprise, nouveau style Discord), mais la boucle principale ne doit pas sentir la plateforme — toute la logique plateforme est donc un adaptateur, et la passerelle ne fait que découpler message et appel agent.
  • La couche capacités est « atomique », la boucle d'apprentissage est « évolutive » — l'atomique ne peut pas être polluée par le chemin évolutif (sinon un bug de background_review modifierait un outil et la boucle principale planterait avec), donc learning_mutations modifie skills/ et non tools/.
  • La planification est transversale : Cron et Subagent sont tous deux « déclencher la boucle principale en dehors de la boucle principale », ils partagent run_conversation mais ne détiennent pas directement l'état de l'agent.

Malentendus courants

  • « Hermes est un framework d'agent » — non. C'est une implémentation concrète d'agent de bureau ; la boucle principale, l'ensemble d'outils, le catalogue de compétences sont livrés avec Hermes. Les capacités de type framework (MCP, Plugin, abstraction Provider) sont des frontières qu'il expose pour pouvoir s'étendre, pas son essence.
  • « La boucle d'apprentissage va automatiquement optimiser les prompts du modèle » — non. Elle ne modifie que les SKILL.md sous skills/ et les mémoires sous ~/.hermes/skills/, pas les templates de system prompt. Les templates de prompt sont des fichiers fixes dans le dépôt, les modifier passe par une release.
  • « Subagent est distribué » — non. Subagent fork l'agent dans le même processus (runtime partagé), le bac à sable est une isolation d'exécution (le code tourne dans Docker/SSH/Modal/Daytona), pas une distribution de l'état de l'agent.
  • « Passerelle = accès aux plateformes de messagerie » — seulement à moitié vrai. La passerelle gère aussi l'état de session (session/), la déduplication de livraison (delivery_ledger), la distribution (dispatch) en streaming (stream_*), les slash commands, les kanban watchers — toutes ces responsabilités vivent « entre la plateforme de messagerie et l'agent », ce n'est pas qu'un point d'accès.

Ordre de lecture suggéré

  1. Démarrage et entrée — comment une commande devient un agent en cours d'exécution
  2. Boucle principale — ce qui se passe à l'arrivée d'un message
  3. Capacités — la différence entre outils et compétences
  4. Passerelle — comment fonctionne la livraison multiplateforme
  5. Planification — automatisation et isolation
  6. Boucle d'apprentissage — le mécanisme d'auto-amélioration

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