Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

AIAgent 内核:同步编排器为何是系统心脏

本章核心源码run_agent.py(全文,重点 416-1140 行初始化、168-210 行 IterationBudget)

定位:本章完整拆解 AIAgent 类——全书最重要的对象。从初始化决策链到回调体系、IterationBudget、fallback provider,理解这个 9431 行的同步编排器如何成为系统心脏。 前置依赖:第 3 章(请求旅程)。适用场景:想理解 AIAgent 的内部设计决策,或准备扩展/修改 agent 行为。

为什么 AIAgent 值得一整章

第 3 章追踪了一次请求穿过系统的旅程,AIAgent.run_conversation() 是旅程的核心。但第 3 章只展示了"消息怎么流动",没有回答"这个编排器为什么这么设计"。

AIAgent 类定义在 run_agent.py:416,它的 __init__ 方法有 45+ 个参数run_agent.py:433-486),初始化代码超过 700 行(到第 1140 行)。这不是膨胀——每个参数和每段初始化代码都对应一个具体的工程问题。本章拆解这些问题和 Hermes 的解法。

run_agent.py 内部结构全景

在深入细节之前,先看这 9431 行代码的内部结构。下图按代码行号从上到下展示了 run_agent.py 的六大区域和它们之间的调用关系:

graph TB
    subgraph "辅助类 (1-415 行)"
        SW["_SafeWriter<br/>broken pipe 防护<br/>111-131"]
        IB["IterationBudget<br/>线程安全迭代计数<br/>168-210"]
        PAR["_should_parallelize_tool_batch<br/>并行安全判断<br/>265-330"]
    end

    subgraph "AIAgent.__init__ (416-1140 行)"
        INIT1["阶段1-3: 配置 + API模式 + 回调"]
        INIT2["阶段4: LLM 客户端构造<br/>Anthropic / OpenAI / Codex"]
        INIT3["阶段5-6: 工具加载 + 记忆初始化"]
        INIT4["阶段7: 压缩器初始化"]
        INIT1 --> INIT2 --> INIT3 --> INIT4
    end

    subgraph "内部方法 (1140-6800 行)"
        BG["_spawn_background_review<br/>后台记忆/技能审查<br/>1718"]
        PERSIST["_persist_session / _flush_messages<br/>Session 持久化<br/>1842-1957"]
        TRAJ["_save_trajectory<br/>轨迹保存<br/>2123"]
        PROMPT["_build_system_prompt<br/>系统提示词拼装<br/>2582"]
        SANITIZE["_sanitize_api_messages<br/>消息清洗与修复<br/>2757"]
        STREAM["_interruptible_streaming_api_call<br/>流式 API 调用 + 健康检测<br/>4282"]
        API_KW["_build_api_kwargs<br/>API 请求构建<br/>5229"]
        FALLBACK["_restore_primary_runtime<br/>Fallback 恢复<br/>4928"]
        COMPRESS["_compress_context<br/>上下文压缩<br/>5835"]
        FLUSH_MEM["flush_memories<br/>记忆刷盘<br/>5677"]
    end

    subgraph "工具执行 (5930-6590 行)"
        EXEC["_execute_tool_calls<br/>入口:并行/串行判断<br/>5930"]
        INVOKE["_invoke_tool<br/>四路分发<br/>5953"]
        CONC["_execute_tool_calls_concurrent<br/>ThreadPoolExecutor<br/>6027"]
        SEQ["_execute_tool_calls_sequential<br/>逐个执行 + 显示<br/>6251"]
        EXEC --> PAR
        EXEC --> CONC
        EXEC --> SEQ
        CONC --> INVOKE
        SEQ --> INVOKE
    end

    subgraph "run_conversation 主循环 (6800-9431 行)"
        RC_PREP["准备阶段<br/>SafeWriter + 清洗 + prompt<br/>+ 预飞行压缩 + hooks<br/>+ memory prefetch"]
        RC_LOOP["主循环<br/>while budget > 0:<br/>  组装消息 → API 调用<br/>  → 解析响应 → 工具执行"]
        RC_EXIT["退出与收尾<br/>persist + sync + hooks<br/>+ background review"]
        RC_PREP --> RC_LOOP --> RC_EXIT
    end

    INIT4 -.-> RC_PREP
    RC_PREP --> PROMPT
    RC_PREP --> COMPRESS
    RC_LOOP --> STREAM
    RC_LOOP --> EXEC
    RC_LOOP --> SANITIZE
    RC_EXIT --> PERSIST
    RC_EXIT --> FLUSH_MEM
    RC_EXIT --> BG

    style RC_LOOP fill:#f96,stroke:#333,stroke-width:3px
    style INIT2 fill:#9cf,stroke:#333,stroke-width:2px
    style EXEC fill:#fc9,stroke:#333,stroke-width:2px

图中高亮:主循环(橙色,2400 行)是系统心脏;LLM 客户端构造(蓝色)决定了与哪个 provider 通信;工具执行(黄色)是能力层的入口。

从图中可以看出 run_agent.py 的结构不是"一个大方法",而是围绕主循环的辐射状架构——主循环调用内部方法,内部方法调用辅助类。理解了这张图,就知道每一段代码在 9431 行中的位置和角色。

AIAgent 初始化的七个阶段

AIAgent.__init__() 的 700 行初始化代码可以分为七个阶段,每个阶段解决一类问题:

graph LR
    A["1. 基础配置<br/>模型、参数、标志"] --> B["2. API 模式<br/>检测与路由"]
    B --> C["3. 回调注册<br/>11 个适配接口"]
    C --> D["4. LLM 客户端<br/>构造与认证"]
    D --> E["5. 工具加载<br/>过滤与验证"]
    E --> F["6. 记忆初始化<br/>builtin + provider"]
    F --> G["7. 压缩器<br/>上下文管理"]

阶段 1:基础配置(run_agent.py:527-553)

# run_agent.py:529-533
self.model = model
self.max_iterations = max_iterations
self.iteration_budget = iteration_budget or IterationBudget(max_iterations)
self.tool_delay = tool_delay
self.quiet_mode = quiet_mode

第一阶段存储基础参数。值得注意的是 iteration_budget:如果调用方提供了 budget(子代理场景),使用传入的实例;否则创建新的。这让父子代理可以使用独立的 budget(详见"IterationBudget"节)。

阶段 2:API 模式检测与路由(run_agent.py:559-590)

Hermes 支持三种 API 模式,检测逻辑如下:

# run_agent.py:559-581(简化)
if api_mode in {"chat_completions", "codex_responses", "anthropic_messages"}:
    self.api_mode = api_mode                         # 显式指定
elif self.provider == "openai-codex":
    self.api_mode = "codex_responses"                # Codex 专用
elif self.provider == "anthropic" or "api.anthropic.com" in base_url:
    self.api_mode = "anthropic_messages"             # Anthropic 原生
elif base_url.endswith("/anthropic"):
    self.api_mode = "anthropic_messages"             # 第三方 Anthropic 兼容
elif self._is_direct_openai_url():
    self.api_mode = "codex_responses"                # 直连 OpenAI 用 Responses API
else:
    self.api_mode = "chat_completions"               # 默认:OpenAI 兼容
API 模式使用场景原因
chat_completionsOpenRouter、大多数第三方最广泛兼容的标准
anthropic_messagesAnthropic 直连、MiniMax、DashScopeAnthropic 原生 API 支持 prompt caching
codex_responsesOpenAI 直连、CodexGPT-5.x 的 tool calling + reasoning 需要 Responses API

这个检测必须在客户端构造之前完成,因为不同 API 模式需要不同的客户端类型(OpenAI SDK vs Anthropic SDK)。

阶段 3:回调注册(run_agent.py:592-602)

# run_agent.py:592-602
self.tool_progress_callback = tool_progress_callback
self.tool_start_callback = tool_start_callback
self.tool_complete_callback = tool_complete_callback
self.thinking_callback = thinking_callback
self.reasoning_callback = reasoning_callback
self.clarify_callback = clarify_callback
self.step_callback = step_callback
self.stream_delta_callback = stream_delta_callback
self.status_callback = status_callback
self.tool_gen_callback = tool_gen_callback
self.background_review_callback = None  # line 546

11 个回调接口,每个服务于一个具体场景:

回调触发时机CLI 用途Gateway 用途
stream_delta_callback模型输出每个 token流式渲染到终端推送到平台
thinking_callbackAPI 调用等待时显示思考动画显示"正在输入..."
tool_start_callback工具开始执行显示工具名发送状态
tool_complete_callback工具执行完成显示结果预览更新状态
clarify_callbackagent 需要用户确认弹出输入框发送消息等待回复
step_callback每轮 API 调用开始触发 agent:step 事件
status_callback状态信息更新推送到平台
tool_progress_callback工具执行中间状态进度条
reasoning_callback模型 reasoning 输出渲染 reasoning
tool_gen_callback工具调用生成中显示工具参数
background_review_callback后台记忆/技能保存完成推送通知到平台

回调的设计原则:编排层决定"什么时候"调用,入口层决定"怎么处理"。这让同一个编排逻辑零修改适配所有入口。如果一个回调是 None,编排层跳过该通知——不会报错,也不会降级。

阶段 4:LLM 客户端构造(run_agent.py:708-828)

这是初始化中最复杂的阶段,分三条路径:

路径 A:Anthropic 原生api_mode == "anthropic_messages"

# run_agent.py:716-730
from agent.anthropic_adapter import build_anthropic_client, resolve_anthropic_token
effective_key = api_key or resolve_anthropic_token() or ""
self._anthropic_client = build_anthropic_client(effective_key, base_url)
self.client = None  # 不需要 OpenAI 客户端

路径 B:显式凭据(调用方直接提供 api_key + base_url)

# run_agent.py:737-744
client_kwargs = {"api_key": api_key, "base_url": base_url}

路径 C:Provider Router 自动解析(无显式凭据)

# run_agent.py:761-763
from agent.auxiliary_client import resolve_provider_client
_routed_client, _ = resolve_provider_client(self.provider or "auto", model=self.model)

三条路径最终都构造出可用的 LLM 客户端。一个重要的细节:如果是 Claude on OpenRouter,会自动注入 x-anthropic-beta: fine-grained-tool-streaming header(run_agent.py:802-812),让 Anthropic 逐 token 流式返回工具调用参数——否则 OpenRouter 的上游代理会在 Claude 沉默思考时超时断开。

阶段 5:工具加载与过滤(run_agent.py:855-883)

# run_agent.py:856-860
self.tools = get_tool_definitions(
    enabled_toolsets=enabled_toolsets,
    disabled_toolsets=disabled_toolsets,
    quiet_mode=self.quiet_mode,
)
self.valid_tool_names = {tool["function"]["name"] for tool in self.tools}

通过 model_tools.pyget_tool_definitions() 获取工具 schema 列表。这个调用触发了完整的工具发现流程(导入所有工具模块 → 自注册 → 按 toolset 过滤)。valid_tool_names 集合后续用于验证模型返回的 tool call 是否合法。

阶段 6:记忆系统初始化(run_agent.py:969-1070)

记忆初始化分两层:

内置记忆run_agent.py:969-992):

# run_agent.py:984-990
if self._memory_enabled or self._user_profile_enabled:
    from tools.memory_tool import MemoryStore
    self._memory_store = MemoryStore(
        memory_char_limit=mem_config.get("memory_char_limit", 2200),
        user_char_limit=mem_config.get("user_char_limit", 1375),
    )
    self._memory_store.load_from_disk()  # 从 MEMORY.md / USER.md 加载

外部 Memory Providerrun_agent.py:996-1070):

# run_agent.py:1028-1054(简化)
if _mem_provider_name:
    self._memory_manager = MemoryManager()
    _mp = load_memory_provider(_mem_provider_name)
    if _mp and _mp.is_available():
        self._memory_manager.add_provider(_mp)
    self._memory_manager.initialize_all(
        session_id=self.session_id,
        platform=platform or "cli",
        user_id=self._user_id,      # Gateway 场景的用户隔离
    )

外部 provider 初始化后,它注册的工具会被追加到 self.tools 列表(run_agent.py:1064-1070)。这意味着 Honcho 或 Mem0 的自定义工具和内置工具以完全相同的方式暴露给模型。

详见第 11 章(Memory Provider 架构)。

阶段 7:上下文压缩器初始化(run_agent.py:1087-1140)

# run_agent.py:1132-1140(简化)
self.context_compressor = ContextCompressor(
    model=self.model,
    threshold_percent=compression_threshold,    # 默认 0.50
    protect_first_n=3,                          # 保护前 3 条消息
    protect_last_n=compression_protect_last,    # 默认保护最后 20 条
    config_context_length=_config_context_length,
)
self.compression_enabled = compression_enabled

压缩器需要知道模型的上下文窗口长度来计算阈值。这个长度的获取有三个来源:config.yaml 显式配置 > custom_providers 配置 > model_metadata 自动探测。

详见第 12 章(上下文压缩)。

IterationBudget:防止 Agent 失控

# run_agent.py:168-209
class IterationBudget:
    """Thread-safe iteration counter for an agent."""

    def __init__(self, max_total: int):
        self.max_total = max_total
        self._used = 0
        self._lock = threading.Lock()

    def consume(self) -> bool:
        """Try to consume one iteration. Returns True if allowed."""
        with self._lock:
            if self._used >= self.max_total:
                return False
            self._used += 1
            return True

    def refund(self) -> None:
        """Give back one iteration (e.g. for execute_code turns)."""
        with self._lock:
            if self._used > 0:
                self._used -= 1

IterationBudget 看起来简单,但它解决了一个关键问题:如何防止 agent 在工具调用循环中无限运转?

设计要点:

  1. 线程安全threading.Lock() 保护计数器,因为 Gateway 场景下多个会话可能在不同线程并发运行
  2. 独立预算:父代理默认 90 次(max_iterations),子代理独立分配(delegation.max_iterations,默认 50 次),互不影响
  3. 退款机制execute_code 工具的调用会被 refund()——因为程序化工具调用是低优先级的,不应消耗面向用户的 budget
  4. 压力预警:当 budget 使用达到 70% 时注入 caution,90% 时注入 warning,提醒模型尽快收敛(run_agent.py:648-650
# run_agent.py:648-650
self._budget_caution_threshold = 0.7   # 70% — nudge to start wrapping up
self._budget_warning_threshold = 0.9   # 90% — urgent, respond now

压力预警不是作为独立消息注入(那会破坏消息结构),而是嵌入到工具结果的 JSON 中——模型在读取工具结果时自然看到预警信息。

Fallback Provider 链

# run_agent.py:830-853
if isinstance(fallback_model, list):
    self._fallback_chain = [
        f for f in fallback_model
        if isinstance(f, dict) and f.get("provider") and f.get("model")
    ]
elif isinstance(fallback_model, dict):
    self._fallback_chain = [fallback_model]
else:
    self._fallback_chain = []

Hermes 支持 fallback provider 链——当主模型不可用(限流、overload、连接失败)时,按顺序尝试备选模型。配置示例:

# config.yaml
fallback_providers:
  - provider: anthropic
    model: claude-sonnet-4-20250514
  - provider: openai
    model: gpt-4o

Fallback 的触发不在初始化阶段,而是在 run_conversation() 的 API 重试逻辑中(run_agent.py:7285+)。当所有重试耗尽后,编排器切换到 fallback chain 的下一个 provider。切换后的状态会在下一轮 run_conversation() 调用时通过 _restore_primary_runtime() 恢复。

背景自我改进:Nudge 与 Background Review

Hermes 的 "Learning Loop" 赌注不只是一个概念,而是编码在 AIAgent 中的具体机制:

# run_agent.py:973-976, 1072-1076
self._memory_nudge_interval = 10    # 每 10 个 user turn 检查一次记忆
self._skill_nudge_interval = 10     # 每 10 个 tool iteration 检查一次技能
self._turns_since_memory = 0
self._iters_since_skill = 0

当计数器达到阈值时,编排器在主循环结束后调用 _spawn_background_review()run_agent.py:1718):

# run_agent.py:1718-1764(简化)
def _spawn_background_review(self, messages_snapshot, review_memory=False, review_skills=False):
    def _run_review():
        review_agent = AIAgent(model=self.model, max_iterations=8, quiet_mode=True)
        review_agent._memory_store = self._memory_store  # 共享记忆存储
        review_agent.run_conversation(user_message=prompt, conversation_history=messages_snapshot)
    threading.Thread(target=_run_review, daemon=True).start()

这个方法:

  1. 创建一个独立的 AIAgent 实例(max_iterations=8,限制 budget)
  2. 共享主 agent 的 _memory_store(让 review agent 可以直接写入记忆)
  3. 后台线程中运行,不阻塞当前响应
  4. 将 review agent 自己的 nudge 设为 0(避免递归触发)
  5. 完成后通过 background_review_callback 通知平台(Gateway 场景下推送到消息平台)

这就是 Hermes 如何在不打断用户的情况下自动审查对话、提炼记忆和创建技能。

中断机制

# run_agent.py:609-611
self._interrupt_requested = False
self._interrupt_message = None
self._client_lock = threading.RLock()

中断机制解决一个交互问题:用户在 agent 执行工具时发送了新消息,或在 CLI 按了 Ctrl+C。编排器需要能安全地中断当前循环,而不是等所有工具执行完毕。

中断传播到子代理:

# run_agent.py:615-617
self._delegate_depth = 0        # 0 = 顶层 agent
self._active_children = []      # 运行中的子代理
self._active_children_lock = threading.Lock()

当顶层 agent 被中断时,它会遍历 _active_children 设置每个子代理的 _interrupt_requested = True,实现递归中断。

活动追踪

# run_agent.py:661-664
self._last_activity_ts: float = time.time()
self._last_activity_desc: str = "initializing"
self._current_tool: str | None = None
self._api_call_count: int = 0

活动追踪不是日志——它是 Gateway 超时处理器的数据源。当 Gateway 因超时杀死 agent 时,这些字段告诉运维人员"agent 被杀时正在做什么",以及"距离上次活动过了多久"。这在调试 agent 卡住问题时极为有用。

run_conversation() 主循环的控制流

初始化完成后,AIAgent 的核心能力通过 run_conversation()run_agent.py:6800)暴露。第 3 章已追踪了消息在主循环中的流动路径,这里从控制流角度补充关键设计:

循环结构

# run_agent.py:7111(简化)
while api_call_count < self.max_iterations and self.iteration_budget.remaining > 0:
    if self._interrupt_requested: break         # 退出条件 1
    if not self.iteration_budget.consume(): break  # 退出条件 2
    
    response = self._interruptible_streaming_api_call(...)
    
    if assistant_message.tool_calls:
        self._execute_tool_calls(...)           # 有工具 → 继续循环
        continue
    else:
        final_response = assistant_message.content  # 无工具 → 退出条件 3
        break

退出条件枚举

退出条件代码位置类型
模型无 tool_callsrun_agent.py:8876正常完成
用户中断run_agent.py:7116正常中断
Budget 耗尽run_agent.py:7125预算限制
context_length_exceeded 压缩失败主循环内 retry错误退出
不可重试 4xx 错误run_agent.py:8295+错误退出
所有重试耗尽run_agent.py:8448+错误退出
finish_reason=length 续写耗尽run_agent.py:7638+边界退出

收尾流程

循环退出后,编排器执行 9 步收尾(run_agent.py:9080+):trajectory 保存 → 资源清理 → post_llm_call hook → session 持久化 → memory_manager.sync_all() → prefetch 排队 → background review(如触发 nudge)→ on_session_end hook → 返回结果。

第 3 章已对主循环和收尾流程做了完整分析,此处仅补充控制流视角。

为什么 run_agent.py 有 9431 行没有拆分

这可能是读者翻开源码时的第一个疑问:一个 9431 行的 Python 文件,129 个方法,为什么不模块化?

先看这 9431 行到底装了什么:

职责行数范围约行数占比
run_conversation() 主循环6800-9200~240025%
__init__ 初始化链433-1140~7007%
工具执行(串行 + 并行)5930-6590~6607%
API 调用与流式处理4170-5230~106011%
错误恢复(重试、fallback、压缩)分散在主循环内~8009%
Session/Trajectory 持久化1840-2400~5606%
System prompt 构建2580-2740~1602%
消息清洗与安全检查2750-3680~93010%
Streaming 解析(Codex/Anthropic/OpenAI)3690-4170~4805%
辅助方法(日志、调试、格式化)散布~6807%
内部类(_SafeWriter, IterationBudget 等)1-416~4154%

不拆分的实际原因

1. 循环引用的工程成本

run_conversation() 的 2400 行主循环直接访问 self._memory_managerself.context_compressorself._session_dbself.toolsself.iteration_budget 等十余个实例属性。如果把主循环拆到 agent/conversation_loop.py,要么传入十几个参数,要么传入 self 本身——后者等于没拆。

Python 的模块系统在处理深度互引时远不如 Rust/Go 的包系统优雅。run_agent.py 的 129 个方法中,约 80% 需要访问 AIAgent 的实例状态。拆分后的每个模块仍然紧密耦合到 AIAgent,带来的不是解耦而是间接性。

2. 主循环的内聚性

run_conversation() 的 2400 行不是因为它做了太多不相关的事,而是因为 agent 对话循环本质上就是复杂的

准备 → API 调用 → 流式解析 → 工具判断 → 并行/串行执行 → 错误恢复
  ↑                                                          |
  └────────── 重试 / 压缩 / fallback / interrupt ←──────────┘

这个循环中的每一步都可能触发错误恢复(API 限流 → 重试 → fallback → 压缩 → 重试),错误恢复的路径又可能回到循环的任何阶段。拆分成独立模块会把这个状态机的边打散,让控制流变得更难追踪。

3. 增量演化的代价

Hermes 从 v0.1 到 v0.8 的 9 个版本中,run_agent.py 持续增长。每个版本添加的功能(prompt caching、fallback 链、预飞行压缩、plugin hooks、Codex Responses API 支持等)都嵌入主循环的不同阶段。重构为模块化架构需要冻结功能开发——对一个快速迭代的开源项目来说,这个代价通常被推迟。

已经拆出去的模块

值得注意的是,Hermes 并非没有模块化意识。agent/ 目录下的 25 个模块就是从 run_agent.py 逐步剥离出来的:

  • agent/prompt_builder.py(983 行):system prompt 的无状态拼装函数
  • agent/prompt_caching.py(72 行):Anthropic cache control 逻辑
  • agent/context_compressor.py(696 行):上下文压缩算法
  • agent/memory_manager.py(367 行):记忆编排
  • agent/retry_utils.py:重试策略
  • agent/model_metadata.py:模型能力探测

这些模块有一个共同特征:它们是无状态或弱状态的,不需要深度访问 AIAgent 实例。能拆的已经拆了,剩下的是确实需要 self 的有状态逻辑。

对读者的建议

不要试图线性阅读 run_agent.py 的 9431 行。推荐的阅读方式:

  1. __init__(416 行)开始,理解初始化的七个阶段(本章已分析)
  2. 跳到 run_conversation()(6800 行),只看主循环骨架(第 3 章已分析)
  3. 按需深入具体的错误恢复路径或工具执行逻辑

这个文件的"正确心理模型"不是"一个巨大的类",而是"一个带有状态的有限状态机,包含了所有状态转换逻辑"。

设计启示

拆解 AIAgent 的初始化与主循环,可以提炼出四个设计原则:

  1. 初始化即决策:50+ 个参数不是配置的堆砌,而是在构造时就确定了 API 模式、prompt caching 策略、工具集合、记忆后端等关键决策。这些决策在整个 run_conversation() 生命周期中不会改变(除非 fallback 触发),让编排逻辑可以安全地做出假设
  2. 回调是适配层的接口:11 个回调让编排层和入口层保持单向依赖——编排层不知道也不关心自己运行在 CLI 还是 Telegram 还是 Cron 中
  3. Budget 是安全网:IterationBudget + 压力预警 + background review 的组合让 agent 既有足够的自主性(90 次迭代),又有收敛机制(70%/90% 预警),还有自我改进能力(background review)——这三者的平衡是 Hermes 作为"长期协作代理"的核心
  4. 有状态内聚优于无状态拆分:9431 行的 run_agent.py 不是设计缺陷而是权衡结果——当 80% 的方法需要深度访问实例状态时,强行模块化带来的不是解耦而是间接性。Hermes 的策略是"能拆的拆出去(agent/ 25 个模块),剩下的保持内聚"

第 5 章将深入 system prompt 的构建过程——_build_system_prompt() 如何将 identity、memory、skills、context files、platform hints 拼装成一个 prompt cache 友好的系统提示。


设计赌注回扣:本章回扣了全部四个赌注:CLI-First(回调体系让 CLI 成为第一等入口)、Run Anywhere(同一 AIAgent 适配所有平台)、Personal Long-Term(记忆初始化 + nudge 机制)、Learning Loop_spawn_background_review 实现自动技能创建)。


版本演化说明

本章核心分析基于 Hermes Agent v0.8.0(2026 年 4 月)。 AIAgent.__init__ 的参数列表随功能持续扩展。独立的 IterationBudget 在 v0.3.0 发布窗口就已出现,_spawn_background_review 的后台审查则在 v0.4.0 发布窗口进入主线;此后的变化主要是围绕 provider 路由、回调面和初始化职责继续堆叠。