AIAgent / init_agent
職務
init_agent は agent の「組み立て工房」:provider、toolsets、圧縮 (compression) 閾値、カスタム provider の extra_body、記憶と学習コンポーネントなどを agent インスタンスに注入し、一往復の対話に必要な依存を全て整える。AIAgent クラス自体はその産物の薄い殻に過ぎない。
設計動機
設定と実行時を分けるのは、「一往復の対話を走らせる」コードパスを可能な限りステートレスにするためだ。init_agent が起動期のすべての意思決定(provider 選択、圧縮戦略、toolsets のオンオフ、extra_body のマージ)を一度に確定させた上で、run_conversation は「メッセージを投げ→ループを回す」リズムに入り、設定に戻って触れない。これにより run_conversation は安心して並行実行できる——同じ agent インスタンスが複数の入站メッセージで同時に駆動されても、「設定が書き換え中」という競合は起きない。
圧縮閾値を圧縮トリガ時ではなく起動期に解析するもう一つの動機:Codex gpt-5.x 系の 272K ウィンドウは、閾値を「自動で引き上げて」ウィンドウを使い切る必要があり、この引き上げは一度だけユーザーに通知したい。実行時に毎回計算すると通知が何度も出るか、抜けが出る。起動期に一度計算して marker ファイルに書き込み、以降は marker を読んで判定する。
主要ファイル
class AIAgent:400-497— クラス定義、フィールド即ち設定__init__ 転送:497-560—from agent.agent_init import init_agent; init_agent(...)def init_agent:276— 組み立てメイン関数入口カスタム provider extra_body 補助:183-275—_normalized_custom_base_url/_custom_provider_model_matches/_merge_custom_provider_extra_body_resolve_compression_threshold:93-127— 圧縮閾値解析補助関数域:62-91—_ra、autoraise notice など
データフロー
AIAgent.__init__(run_agent.py:423) は構築引数をそのまま収集する。init_agent:276を呼び、順に:- provider 名と base_url を解析(
agent/agent_init.py:183) - カスタム provider の
extra_bodyをマージ(agent/agent_init.py:257) - 圧縮閾値を解析(
agent/agent_init.py:93) - toolsets、記憶マネージャ、学習グラフなどを注入
- provider 名と base_url を解析(
- 組み立て完了した agent は
run_conversationメソッド(run_agent.py:6350)を露出し、モジュールレベルのagent/conversation_loop.py:588に転送する。 - ゲートウェイの
GatewayRunner(gateway/run.py:3029)が組み立て済み agent を保持し、入站メッセージを渡す。
provider 名は init_agent 内で小文字に規整され、空白は削除される。provider はルーティングヒントにも api_mode 推断の根拠にも使われる。
agent.base_url = base_url or ""
provider_name = provider.strip().lower() if isinstance(provider, str) and provider.strip() else None
agent.provider = provider_name or ""
...
if api_mode in {"chat_completions", "codex_responses", "anthropic_messages", "bedrock_converse", "codex_app_server"}:
agent.api_mode = api_mode
elif agent.provider == "openai-codex":
agent.api_mode = "codex_responses"
elif agent.provider in {"xai", "xai-oauth"}:
agent.api_mode = "codex_responses"圧縮閾値の解析は _resolve_compression_threshold で行う。Codex の autoraise は一方向だ——ユーザーが設定した高い閾値を下げることはなく、上げるだけできる。
if model_cthresh is None:
return global_threshold, None
if is_codex_autoraise:
if model_cthresh <= global_threshold + 1e-9:
# Autoraise never lowers; keep the user's higher/equal threshold.
return global_threshold, None
return model_cthresh, {
"model": model,
"from": global_threshold,
"to": model_cthresh,
}
return model_cthresh, Noneカスタム provider の extra_body マージの核心は、base_url + model で設定項目を突き合わせ、見つければ extra_body を request_overrides に取り込むことだ。既存のキーはユーザーが自分で設定した値を優先して保持する。
merged_extra_body = dict(extra_body)
existing_extra_body = overrides.get("extra_body")
if isinstance(existing_extra_body, dict):
merged_extra_body.update(existing_extra_body)
overrides["extra_body"] = merged_extra_body
agent.request_overrides = overridesupdate の向きは「設定の extra_body を底に、ユーザーが既に持っている extra_body を上に重ねる」だ。だから実行時に request_overrides["extra_body"] に差し込んだフィールドが起動設定で上書きされることはない。
境界と失敗
- provider 名から api_mode が推断できない:ユーザーが未知の
provider="foobar"を渡すと、api_modeは推断されず、デフォルトの chat_completions パスに落ちる。もし foobar が実際には codex_responses しかサポートしなければ、最初のメッセージで 400 が返る。init_agentは provider がホワイトリストにあるかを検証せず、大小文字の規整だけを行う。 - custom extra_body のマージ衝突:同じ base_url に複数の entry があり、同じ model にマッチした場合、
_custom_provider_extra_body_for_agentは「最初の model 明示マッチが勝ち」とし、以降は無視する。entry にmodelフィールドがないものは fallback 扱いで、明示的な model マッチ項目がないときだけ使われる。ユーザーが設定を変えたが agent を再起動せず、新しい extra_body が効いていると思い込んでいると、実際には古い fallback がまだ効いていることがある。 - 圧縮閾値の不正値:
global_thresholdがNoneまたは負の数のとき、_resolve_compression_thresholdは範囲検証を行わず、そのまま透過する。負の数なら圧縮は永遠に発火せず、Noneなら比較時にTypeErrorが飛ぶ。設定層(hermes config)には範囲チェックがあるが、init_agentは重複検証せず、config の産物をそのまま信頼する。 - autoraise notice の marker 書き込み失敗:
$HERMES_HOMEが読み取り専用またはディスク一杯のとき、_record_codex_gpt55_autoraise_noticeは黙ってスキップし、代償として次回 init で通知が再度出る。これは意図的なトレードオフで、marker 書き込みの失敗で agent が起動できなくなるのは避けたい。 _ra()の遅延 import:init_agent内の補助関数は_ra()でrun_agentモジュールを取得し、トップレベルでimportしない。これはテストがrun_agent.OpenAIなどの属性を monkeypatch してからinit_agentを呼べるようにするためだ。代償として初回呼び出しに一回の import が乗り、patch のタイミングが遅れると既に束縛された属性は新しい patch で上書きされない。
まとめ
init_agent は設定→実行時の翻訳層であり、起動期の意思決定(provider 選択、圧縮戦略、toolsets のオンオフ)はすべてここで一度に確定する。以後 agent はステートレスな「メッセージを投げ→ループを回す」リズムに入り、設定を戻って変更することはない。