エントリと起動フロー
職務
「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 する。
主要ファイル
ゲートウェイ main:22965— ゲートウェイプロセス入口main()AIAgent クラス:400-497— 薄い入口クラス、__init__は引数を格納するだけAIAgent.__init__ 転送:497-560— コメントに "Forwarder — see agent.agent_init.init_agent" と明記run_conversation メソッド:6350—AIAgent.run_conversationインスタンスメソッド、モジュールレベル run_conversation:588を呼ぶCLI main:6434— CLI 入口init_agent:276-400— 依存注入メイン関数_resolve_compression_threshold:93-127— 起動期設定解析の例hermes_bootstrap.py— 最初期の環境/パス調整(リポジトリルート)
データフロー
- ユーザーが
hermes(CLI)またはゲートウェイmain()を起動(run_agent.py:22965)。 - プロセスは
hermes_bootstrap.pyでパス/環境を修正してから、CLI main:6434またはゲートウェイループに入る。 AIAgent:400インスタンスを生成、__init__はinit_agent:276に転送。init_agentは provider、toolsets、圧縮閾値、カスタム provider の extra_body などを解析し、実行可能な agent オブジェクトを組み立てる。- ゲートウェイは
gateway/run.pyのGatewayRunnerでこの agent を保持し、入站メッセージごとにrun_conversation:588に渡す。
AIAgent.__init__ の関数体は「転送器」という三文字の文字通りだ——構築パラメータを一つ残らず init_agent に透過し、自分は何も解析しない。
"""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 では素通りする。短いロジックだ。
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(テストシナリオ)や既に閉じている場合、reconfigureはOSError/ValueErrorを投げ、コードは catch して黙ってスキップする。環境変数のパスは依然有効なので、子プロセスには影響しない。
まとめ
エントリ層は「転送 + 組み立て」であり、業務ロジックを持たない。AIAgent は薄い殻、init_agent こそが組み立ての中心。この流れを理解すれば、次のエージェント主ループは「メッセージを投げられた後何をするか」に過ぎない。