代理循环内部机制
核心编排引擎是 run_agent.py 的 AIAgent 类 — 大约 10,700 行,处理从提示组装到工具调度再到大模型提供商(provider)故障转移的所有内容。
核心职责
AIAgent 负责:
- 通过
prompt_builder.py组装有效的系统提示和工具模式 - 选择正确的提供商/API 模式(chat_completions、codex_responses、anthropic_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 执行模式,通过大模型提供商(provider)选择、显式参数和基本 URL 启发式解析:
| API模式 | 用于 | 客户类型 |
|---|---|---|
TODO | OpenAI 兼容端点(OpenRouter、自定义、大多数提供商) | TODO |
TODO | OpenAI Codex / 响应 API | openai.OpenAI 具有响应格式 |
TODO | 原生人类消息 API | anthropic.Anthropic 通过适配器 |
该模式决定消息的格式、工具调用的结构、响应的解析方式以及缓存/流的工作方式。在 API 调用之前和之后,这三者都集中在相同的内部消息格式(OpenAI 风格的 role/content/tool_calls 字典)。
模式解析顺序:
1.显式api_mode构造函数arg(最高优先级)
2. 特定于大模型提供商(provider)的检测(例如,anthropic 大模型提供商(provider) → anthropic_messages)
3. 基本 URL 启发式(例如 api.anthropic.com → anthropic_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 (_interruptible_api_call)
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 (with tool_calls) → Tool → Tool → ... → Assistant - 绝不连续两条助理消息
- 从不连续两条用户消息
- 仅
tool角色可以有连续条目(并行工具结果)
大模型提供商(provider)验证这些序列并将拒绝畸形的历史记录。
可中断的 API 调用
API 请求包装在 _interruptible_api_call() 中,它在后台线程中运行实际的 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 | 读取/写入代理本地任务状态 |
TODO | 写入具有字符限制的持久内存文件 |
TODO | 通过代理的会话数据库查询会话历史记录 |
TODO | 生成具有隔离上下文的子代理 |
这些工具直接修改代理状态并返回综合工具结果,而无需通过注册表。
回调表面
AIAgent 支持特定于平台的回调,可实现 CLI、网关和 ACP 集成的实时进度:
| 回拨 | 被解雇时 | 使用者 |
|---|---|---|
TODO | 每个工具执行之前/之后 | CLI 微调器、网关进度消息 |
TODO | 当模型开始/停止思考时 | CLI“思考...”指示器 |
TODO | 当模型返回推理内容时 | CLI推理显示、网关推理块 |
TODO | 当调用 clarify 工具时 | CLI输入提示、网关交互消息 |
TODO | 每次完成代理轮后 | 网关步骤跟踪、ACP 进度 |
TODO | 每个流令牌(启用时) | CLI 流式显示 |
TODO | 当从流中解析工具调用时 | 微调器中的 CLI 工具预览 |
TODO | 状态变化(思考、执行等) | ACP 状态更新 |
预算和后备行为
迭代预算
代理通过 IterationBudget 跟踪迭代:
- 默认:90 次迭代(可通过
agent.max_turns配置) - 每个代理都有自己的预算。子代理的独立预算上限为
delegation.max_iterations(默认 50) — 父代理 + 子代理的总迭代次数可能会超过父代理的上限 - 达到 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(压缩创建“子”会话)
会话持续性
每回合后:
- 消息保存到会话存储(SQLite 通过
hermes_state.py) - 内存更改刷新到
MEMORY.md/USER.md - 稍后可以通过
/resume或hermes chat --resume恢复会话
关键源文件
| 文件 | 目的 |
|---|---|
TODO | AIAgent 类 — 完整的代理循环(约 10,700 行) |
TODO | 系统提示从记忆、技能、背景文件、性格中拼装 |
TODO | ContextEngine ABC — 可插入上下文管理 |
TODO | 默认引擎——有损摘要算法 |
TODO | 人为提示缓存标记和缓存指标 |
TODO | 用于辅助任务(愿景、总结)的辅助LLM客户端 |
TODO | 工具模式集合,handle_function_call() 调度 |