Skip to content

エントリと起動フロー

源码版本v2026.7.20

職務

hermes コマンド / ゲートウェイ (gateway) のメッセージ一つ」を「メモリ上で動く agent」に変える。エントリ層は意図的に薄く作ってある——run_agent.py は転送と CLI 組み立てしか扱わず、本当の依存注入は agent_init.py にある。これによりゲートウェイ、TUI、CLI、ACP、cron が同じ初期化パスを共有できる。

設計動機

エントリ層を薄く、組み立てを集中させるのは、すべての呼び出しパスが一つだけのやり方を学べば済むようにするためだ。もし AIAgent.__init__ が自分で provider を解き、extra_body をマージし、圧縮 (compression) 閾値を計算していたら、ゲートウェイ、TUI、cron が agent を走らせるたびに同じロジックを復刻しなければならず、一箇所の変更が複数箇所の修正を引き起こす。__init__ を転送器に退化させ、本当の意思決定を init_agent 一つの関数に集めることで、新しいフロントエンド(将来の MCP デーモンなど)を追加しても AIAgent を構築するだけで全機能が手に入り、組み立ての詳細を知らなくて済む。

hermes_bootstrap.py を最初に置くのも同じ発想だ。Windows の UTF-8 対応は「早ければ早いほど良い」環境修正で、サブプロセスが派生してからでは間に合わない。だから独立したモジュールにして、すべてのエントリポイントの先頭行で import する。

主要ファイル

データフロー

  1. ユーザーが hermes(CLI)またはゲートウェイ main() を起動(run_agent.py:22965)。
  2. プロセスは hermes_bootstrap.py でパス/環境を修正してから、CLI main:6434 またはゲートウェイループに入る。
  3. AIAgent:400 インスタンスを生成、__init__init_agent:276 に転送。
  4. init_agent は provider、toolsets、圧縮閾値、カスタム provider の extra_body などを解析し、実行可能な agent オブジェクトを組み立てる。
  5. ゲートウェイは gateway/run.pyGatewayRunner でこの agent を保持し、入站メッセージごとに run_conversation:588 に渡す。

AIAgent.__init__ の関数体は「転送器」という三文字の文字通りだ——構築パラメータを一つ残らず init_agent に透過し、自分は何も解析しない。

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

一方、プロセスに最初に触れる hermes_bootstrap.py は Windows でのみエンコーディングのフォールバック (fallback) を入れ、POSIX では素通りする。短いロジックだ。

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 を使って = にしないのは意図的だ。ユーザーが事前に PYTHONUTF8=0 を環境変数に入れて opt-out できるようにし、上書きしない。

境界と失敗

  • エントリが直接構築されるケース:誰かが hermes_bootstrap を飛ばして Windows で python -m gateway.run を起動すると、stdio は cp1252 のままで非 ASCII 出力が UnicodeEncodeError を起こす。bootstrap の位置は慣例であって強制ではなく、すべてのエントリポイントの先頭行で import することがドキュメントの前提になっている。
  • AIAgent.__init__init_agent の前に例外を投げる:関数体は import + 呼び出しだけなので、失敗はすべて init_agent 内部由来で、スタックフレームが「ずれて」見える。デバッグ時、traceback の最後のフレームは init_agent になる。__init__ の「転送器」というコメントに惑わされて「転送器自体のバグ」と誤読しないこと。
  • init_agent の import は遅延: from agent.agent_init import init_agent をモジュールトップではなく __init__ 関数体に書くのは循環 import 回避のため——agent_init が逆に run_agent を import するからだ。代償として AIAgent 初回構築時に一回の import オーバーヘッド(ミリ秒級)が乗るが、以降はモジュールキャッシュされる。
  • hermes_bootstrap の reconfigure は失敗しうる: stdout が BytesIO(テストシナリオ)や既に閉じている場合、reconfigureOSError/ValueError を投げ、コードは catch して黙ってスキップする。環境変数のパスは依然有効なので、子プロセスには影響しない。

まとめ

エントリ層は「転送 + 組み立て」であり、業務ロジックを持たない。AIAgent は薄い殻、init_agent こそが組み立ての中心。この流れを理解すれば、次のエージェント主ループは「メッセージを投げられた後何をするか」に過ぎない。

非公式コミュニティ学習サイト。MIT ライセンスの NousResearch/hermes-agent ソースに基づく。