跳到主要内容

Agent Loop 内部机制

核心编排引擎是 run_agent.py 中的 AIAgent 类 —— 约 9,200 行代码,负责从提示词组装到工具分发,再到提供方故障转移的全部流程。

核心职责

AIAgent 负责以下事项:

  • 通过 prompt_builder.py 组装有效系统提示词和工具模式
  • 选择正确的提供方/API 模式(chat_completionscodex_responsesanthropic_messages
  • 支持中断的模型调用,具备取消支持
  • 执行工具调用(通过线程池实现串行或并发执行)
  • 以 OpenAI 消息格式维护对话历史
  • 处理压缩、重试以及备用模型切换
  • 跟踪父代理和子代理之间的迭代预算
  • 在上下文丢失前刷新持久化内存

两个入口点

# Simple interface — returns final response string
response = agent.chat("Fix the bug in main.py")

# Full interface — returns dict with messages, metadata, usage stats
result = agent.run_conversation(
user_message="Fix the bug in main.py",
system_message=None, # auto-built if omitted
conversation_history=None, # auto-loaded from session if omitted
task_id="task_abc123"
)

chat()run_conversation() 的轻量封装,从结果字典中提取 final_response 字段。

API 模式

Hermes 支持三种 API 执行模式,由提供方选择、显式参数和基础 URL 推断共同决定:

API 模式用途客户端类型
chat_completionsOpenAI 兼容端点(OpenRouter、自定义、大多数提供方)openai.OpenAI
codex_responsesOpenAI Codex / Responses APIopenai.OpenAI(使用 Responses 格式)
anthropic_messages原生 Anthropic Messages APIanthropic.Anthropic 通过适配器

模式决定了消息格式、工具调用结构、响应解析方式以及缓存/流式处理机制。三种模式在 API 调用前后均统一为相同的内部消息格式(OpenAI 风格的 role/content/tool_calls 字典)。

模式解析顺序:

  1. 显式 api_mode 构造函数参数(优先级最高)
  2. 提供方特定检测(例如 anthropic 提供方 → anthropic_messages
  3. 基础 URL 推断(例如 api.anthropic.comanthropic_messages
  4. 默认值:chat_completions

轮次生命周期

代理循环的每次迭代遵循以下流程:

run_conversation()
1. Generate task_id if not provided
2. Append user message to conversation history
3. Build or reuse cached system prompt (prompt_builder.py)
4. Check if preflight compression is needed (>50% context)
5. Build API messages from conversation history
- chat_completions: OpenAI format as-is
- codex_responses: convert to Responses API input items
- anthropic_messages: convert via anthropic_adapter.py
6. Inject ephemeral prompt layers (budget warnings, context pressure)
7. Apply prompt caching markers if on Anthropic
8. Make interruptible API call (_api_call_with_interrupt)
9. Parse response:
- If tool_calls: execute them, append results, loop back to step 5
- If text response: persist session, flush memory if needed, return

消息格式

所有消息在内部均使用 OpenAI 兼容格式:

{"role": "system", "content": "..."}
{"role": "user", "content": "..."}
{"role": "assistant", "content": "...", "tool_calls": [...]}
{"role": "tool", "tool_call_id": "...", "content": "..."}

支持扩展思考的模型生成的推理内容存储在 assistant_msg["reasoning"] 中,并可通过 reasoning_callback 可选显示。

消息交替规则

代理循环强制执行严格的消息角色交替:

  • 系统消息之后:User → Assistant → User → Assistant → ...
  • 工具调用期间:Assistant(带 tool_calls)→ Tool → Tool → ... → Assistant
  • 绝不允许连续两个助理消息
  • 绝不允许连续两个用户消息
  • 仅允许 tool 角色拥有连续条目(并行工具结果)

提供方会验证这些序列,拒绝格式错误的历史记录。

可中断的 API 调用

API 请求被封装在 _api_call_with_interrupt() 中,该函数在后台线程中运行实际的 HTTP 调用,同时监控中断事件:

┌──────────────────────┐     ┌──────────────┐
│ Main thread │ │ API thread │
│ wait on: │────▶│ HTTP POST │
│ - response ready │ │ to provider │
│ - interrupt event │ └──────────────┘
│ - timeout │
└──────────────────────┘

当发生中断时(用户发送新消息、执行 /stop 命令或接收信号):

  • API 线程被放弃(响应被丢弃)
  • 代理可处理新输入或干净关闭
  • 不会将部分响应注入对话历史

工具执行

串行与并发

当模型返回工具调用时:

  • 单个工具调用 → 在主线程中直接执行
  • 多个工具调用 → 通过 ThreadPoolExecutor 并发执行
    • 特例:标记为交互式(如 clarify)的工具强制串行执行
    • 无论完成顺序如何,结果均按原始工具调用顺序重新插入

执行流程

for each tool_call in response.tool_calls:
1. Resolve handler from tools/registry.py
2. Fire pre_tool_call plugin hook
3. Check if dangerous command (tools/approval.py)
- If dangerous: invoke approval_callback, wait for user
4. Execute handler with args + task_id
5. Fire post_tool_call plugin hook
6. Append {"role": "tool", "content": result} to history

代理级工具

某些工具在到达 handle_function_call() 之前由 run_agent.py 拦截:

工具拦截原因
todo读取/写入代理本地任务状态
memory向持久化内存文件写入,带字符限制
session_search通过代理的会话数据库查询会话历史
delegate_task启动子代理(s),拥有隔离上下文

这些工具直接修改代理状态,并返回合成的工具结果,不经过注册表。

回调接口

AIAgent 支持平台特定的回调,以在 CLI、网关和 ACP 集成中实现实时进度反馈:

回调函数触发时机使用方
tool_progress_callback每个工具执行前后CLI 进度条,网关进度消息
thinking_callback模型开始/停止思考时CLI “thinking...” 指示器
reasoning_callback模型返回推理内容时CLI 推理显示,网关推理块
clarify_callback调用 clarify 工具时CLI 输入提示,网关交互消息
step_callback每次完整的代理回合结束后网关步骤追踪,ACP 进度
stream_delta_callback每次流式传输的 token(启用时)CLI 流式显示
tool_gen_callback从流中解析出工具调用时CLI 进度条中的工具预览
status_callback状态变化时(思考、执行等)ACP 状态更新

预算与回退行为

迭代预算

代理通过 IterationBudget 跟踪迭代次数:

  • 默认值:90 次迭代(可通过 agent.max_turns 配置)
  • 父代理与子代理共享预算 —— 子代理会消耗父代理的预算
  • 两级预算压力机制通过 _get_budget_warning() 实现:
    • 达到 70% 以上使用率(警告级别):在最后一个工具结果中追加 [BUDGET: 迭代 X/Y。剩余 N 次迭代。开始整合你的工作。]
    • 达到 90% 以上使用率(严重警告级别):在最后一个工具结果中追加 [BUDGET WARNING: 迭代 X/Y。仅剩 N 次迭代。立即提供最终响应。]
  • 达到 100% 时,代理停止并返回已完成工作的摘要

回退模型

当主模型失败时(429 速率限制、5xx 服务器错误、401/403 认证错误):

  1. 检查配置中的 fallback_providers 列表
  2. 按顺序尝试每个回退提供方
  3. 成功后,使用新提供方继续对话
  4. 对于 401/403 错误,在切换前尝试刷新凭证

回退系统也独立覆盖辅助任务 —— 视觉、压缩、网页提取和会话搜索各自拥有可配置的独立回退链,通过 auxiliary.* 配置节进行设置。

压缩与持久化

压缩触发时机

  • 预检(API 调用前):当对话超过模型上下文窗口的 50%
  • 网关自动压缩:当对话超过 85%(更激进,运行于回合之间)

压缩期间发生的情况

  1. 首先将内存刷新到磁盘(防止数据丢失)
  2. 将中间对话回合总结为紧凑摘要
  3. 保留最后 N 条消息完整(compression.protect_last_n,默认值:20)
  4. 工具调用/结果消息对保持完整(从不拆分)
  5. 生成新的会话谱系 ID(压缩创建了一个“子”会话)

会话持久化

每次回合结束后:

  • 消息保存到会话存储(通过 hermes_state.py 使用 SQLite)
  • 内存更改刷新到 MEMORY.md / USER.md
  • 可通过 /resumehermes chat --resume 重新启动会话

关键源文件

文件用途
run_agent.pyAIAgent 类 —— 完整的代理循环(约 9,200 行)
agent/prompt_builder.py从记忆、技能、上下文文件、个性等组装系统提示
agent/context_engine.pyContextEngine ABC —— 可插拔的上下文管理
agent/context_compressor.py默认引擎 —— 有损摘要算法
agent/prompt_caching.pyAnthropic 提示缓存标记与缓存指标
agent/auxiliary_client.py辅助 LLM 客户端,用于辅助任务(视觉、摘要)
model_tools.py工具模式集合,handle_function_call() 分发逻辑