AIAgent / init_agent
Responsibility
init_agent is the agent's "assembly workshop": it injects provider, toolsets, compression threshold, custom provider extra_body, memory and learning components into the agent instance so it has every dependency needed to run a turn of conversation. The AIAgent class itself is just a thin shell around its output.
Design Motivation
Config and runtime are kept separate so that the "run one turn of conversation" code path stays as stateless as possible. init_agent settles every startup-time decision (provider choice, compression strategy, toolsets toggles, extra_body merge) once and for all; afterwards run_conversation just gets on with "feed message → run loop" and never reaches back to mutate config. The direct payoff: run_conversation can be driven concurrently — when the same agent instance is fed by multiple inbound messages at once, there's no "config is being mutated" race.
Resolving the compression threshold at startup rather than when compression fires has another motive: the 272K window on the Codex gpt-5.x family needs the threshold "auto-raised" to use the full window, and that raise should be announced to the user once. Computing it at runtime would either spam notifications or miss them. Compute once at startup, write a marker file, and read the marker afterward to decide.
Key Files
class AIAgent:400-497— class definition; fields are config__init__ forwarder:497-560—from agent.agent_init import init_agent; init_agent(...)def init_agent:276— assembly main function entrycustom provider extra_body helpers:183-275—_normalized_custom_base_url/_custom_provider_model_matches/_merge_custom_provider_extra_body_resolve_compression_threshold:93-127— compression threshold resolutionhelper function region:62-91—_ra, autoraise notice, etc.
Data Flow
AIAgent.__init__(run_agent.py:423) collects constructor parameters as-is.- It calls
init_agent:276, which in order:- resolves the provider name and base_url (
agent/agent_init.py:183) - merges the custom provider's
extra_body(agent/agent_init.py:257) - resolves the compression threshold (
agent/agent_init.py:93) - injects toolsets, memory manager, learning graph, etc.
- resolves the provider name and base_url (
- The assembled agent exposes the
run_conversationmethod (run_agent.py:6350), forwarding to the module-levelagent/conversation_loop.py:588. - The gateway's
GatewayRunner(gateway/run.py:3029) holds the assembled agent and hands inbound messages to it.
Inside init_agent the provider name is normalized to lowercase with whitespace stripped; provider is used both as a routing hint and as the basis for inferring 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"The compression threshold is resolved in _resolve_compression_threshold. Codex's autoraise is one-way — it can only raise the threshold, never lower a higher value the user already set:
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, NoneThe merge of a custom provider's extra_body centers on matching the config entry by base_url + model, then folding its extra_body into request_overrides. Existing keys are preserved, with the user's own values taking priority:
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 = overridesThe direction of update is "config's extra_body is the base, the user's existing extra_body overlays it", so any field placed into request_overrides["extra_body"] at runtime is not overwritten by startup config.
Boundaries and Failure
- Provider name can't resolve to an api_mode: If the user passes an unknown
provider="foobar",api_modeis not inferred and falls back to the defaultchat_completionspath. If foobar actually only supportscodex_responses, the first message at runtime will 400.init_agentdoes not validate whether the provider is on a whitelist; it only normalizes case. - custom extra_body merge conflicts: When multiple entries are configured under the same base_url and all match the same model,
_custom_provider_extra_body_for_agentpicks "the first explicit model match wins"; later ones are ignored. An entry without amodelfield acts as a fallback, used only when no explicit model match exists. If a user edits config without restarting the agent, they may think the newextra_bodyis in effect when the old fallback still is. - Illegal compression threshold values: If
global_thresholdisNoneor negative,_resolve_compression_thresholddoes no range validation and passes it through. A negative number means compression never fires;NoneraisesTypeErroron comparison. The config layer (hermes config) does range-check, butinit_agentdoes not duplicate that and trusts the config product. - autoraise notice marker write failure: If
$HERMES_HOMEis read-only or the disk is full,_record_codex_gpt55_autoraise_noticesilently skips; the cost is that the next init pops the notification again. This is an intentional trade-off: a marker write failure should not keep the agent from starting. - Lazy import in
_ra(): Helpers ininit_agentreach therun_agentmodule through_ra()rather than a top-levelimport, so tests can monkeypatch attributes likerun_agent.OpenAIbefore callinginit_agent. The cost is one extra import on first call; if a patch lands after attributes are already bound, the bound ones aren't overridden by the new patch.
Summary
init_agent is the translation layer from config to runtime; every startup-time decision (provider selection, compression strategy, toolsets toggles) is settled here once. Afterward the agent enters a stateless "feed message → run loop" rhythm and no longer revisits config.