先复习一下hermes架构┌─────────────────────────────────────────────────────────────────────┐ │ Entry Points │ │ │ │ CLI (cli.py) Gateway (gateway/run.py) ACP (acp_adapter/) │ │ Batch Runner API Server Python Library │ └──────────┬──────────────┬───────────────────────┬───────────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ AIAgent (run_agent.py) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Prompt │ │ Provider │ │ Tool │ │ │ │ Builder │ │ Resolution │ │ Dispatch │ │ │ │ (prompt_ │ │ (runtime_ │ │ (model_ │ │ │ │ builder.py) │ │ provider.py)│ │ tools.py) │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌──────┴───────┐ ┌──────┴───────┐ ┌──────┴───────┐ │ │ │ Compression │ │ 3 API Modes │ │ Tool Registry│ │ │ │ Caching │ │ chat_compl. │ │ (registry.py)│ │ │ │ │ │ codex_resp. │ │ 70 tools │ │ │ │ │ │ anthropic │ │ 28 toolsets │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────┴─────────────────┴─────────────────┴───────────────────────┘ │ │ ▼ ▼ ┌───────────────────┐ ┌──────────────────────┐ │ Session Storage │ │ Tool Backends │ │ (SQLite FTS5) │ │ Terminal (7 backends) │ │ hermes_state.py │ │ Browser (5 backends) │ │ gateway/session.py│ │ Web (4 backends) │ └───────────────────┘ │ MCP (dynamic) │ │ File, Vision, etc. │ └──────────────────────┘核心编排引擎是run_agent.py中的AIAgent类——这是一个大型文件15k 行 新版本已经重构 只有5k负责处理从 prompt提示词组装到工具分发再到 provider 故障转移的所有逻辑。核心职责AIAgent负责通过prompt_builder.py组装有效的系统 prompt 和工具 schema选择正确的 provider/API 模式chat_completions、codex_responses、anthropic_messages发起支持取消操作的可中断模型调用执行工具调用顺序执行或通过线程池并发执行以 OpenAI 消息格式维护对话历史处理压缩、重试和回退模型切换跨父 agent 和子 agent 追踪迭代预算在上下文丢失前将持久化内存刷写到磁盘两个入口点# 简单接口——返回最终响应字符串 response agent.chat(Fix the bug in main.py) # 完整接口——返回包含消息、元数据、用量统计的 dict result agent.run_conversation( user_messageFix the bug in main.py, system_messageNone, # 省略时自动构建 conversation_historyNone, # 省略时自动从 session 加载 task_idtask_abc123 )chat()是对run_conversation()的轻量封装从结果 dict 中提取final_response字段。API 模式Hermes 支持三种 API 执行模式通过 provider 选择、显式参数和 base URL 启发式规则来确定API 模式用途客户端类型chat_completions兼容 OpenAI 的端点OpenRouter、自定义及大多数 provideropenai.OpenAIcodex_responsesOpenAI Codex / Responses APIopenai.OpenAI使用 Responses 格式anthropic_messages原生 Anthropic Messages API通过适配器使用anthropic.Anthropic模式决定了消息的格式化方式、工具调用的结构、响应的解析方式以及缓存/流式传输的工作方式。三种模式在 API 调用前后均收敛到相同的内部消息格式OpenAI 风格的role/content/tool_callsdict。模式解析顺序显式api_mode构造函数参数最高优先级Provider 特定检测例如anthropicprovider →anthropic_messagesBase URL 启发式规则例如api.anthropic.com→anthropic_messages默认chat_completions单轮生命周期agent loop 的每次迭代按以下顺序执行run_conversation() 1. 若未提供则生成 task_id 2. 将用户消息追加到对话历史 3. 构建或复用已缓存的系统 promptprompt_builder.py 4. 检查是否需要预检压缩上下文超过 50% 5. 从对话历史构建 API 消息 - chat_completions直接使用 OpenAI 格式 - codex_responses转换为 Responses API 输入项 - anthropic_messages通过 anthropic_adapter.py 转换 6. 注入临时 prompt 层预算警告、上下文压力提示 7. 若使用 Anthropic应用 prompt 缓存标记 8. 发起可中断的 API 调用_interruptible_api_call 9. 解析响应 - 若有 tool_calls执行工具追加结果回到步骤 5 - 若为文本响应持久化 session按需刷写内存返回消息格式所有消息在内部均使用兼容 OpenAI 的格式{role: system, content: ...} {role: user, content: ...} {role: assistant, content: ..., tool_calls: [...]} {role: tool, tool_call_id: ..., content: ...}推理内容来自支持扩展思考的模型存储在assistant_msg[reasoning]中并可选择通过reasoning_callback展示。消息交替规则agent loop 强制执行严格的消息角色交替规则系统消息之后User → Assistant → User → Assistant → ...工具调用期间Assistant含 tool_calls→ Tool → Tool → ... → Assistant不允许连续出现两条 assistant 消息不允许连续出现两条 user 消息只有tool角色可以连续出现并行工具结果Provider 会验证这些序列并拒绝格式错误的历史记录。可中断的 API 调用API 请求被封装在_interruptible_api_call()中该方法在后台线程中执行实际的 HTTP 调用同时监听中断事件┌────────────────────────────────────────────────────┐ │ 主线程 API 线程 │ │ │ │ 等待 HTTP POST │ │ - 响应就绪 ───▶ 发送至 provider │ │ - 中断事件 │ │ - 超时 │ └────────────────────────────────────────────────────┘当发生中断用户发送新消息、/stop命令或信号时API 线程被放弃响应被丢弃agent 可以处理新输入或干净地关闭不会将部分响应注入对话历史工具执行顺序执行与并发执行当模型返回工具调用时单个工具调用→ 直接在主线程中执行多个工具调用→ 通过ThreadPoolExecutor并发执行例外标记为交互式的工具如clarify强制顺序执行无论完成顺序如何结果均按原始工具调用顺序重新插入执行流程for each tool_call in response.tool_calls: 1. 从 tools/registry.py 解析处理器 2. 触发 pre_tool_call 插件 hook 3. 检查是否为危险命令tools/approval.py - 若危险调用 approval_callback等待用户确认 4. 使用参数 task_id 执行处理器 5. 触发 post_tool_call 插件 hook 6. 将 {role: tool, content: result} 追加到历史Agent 级工具部分工具在到达handle_function_call()之前由run_agent.py提前拦截工具拦截原因todo读写 agent 本地任务状态memory向持久化内存文件写入内容有字符限制session_search通过 agent 的 session DB 查询 session 历史delegate_task以隔离上下文生成子 agent这些工具直接修改 agent 状态并返回合成的工具结果不经过注册表。回调接口AIAgent支持平台特定的回调用于在 CLI、gateway 和 ACP 集成中实现实时进度展示回调触发时机使用方tool_progress_callback每次工具执行前后CLI spinner、gateway 进度消息thinking_callback模型开始/停止思考时CLI thinking... 指示器reasoning_callback模型返回推理内容时CLI 推理展示、gateway 推理块clarify_callback调用clarify工具时CLI 输入提示、gateway 交互消息step_callback每次完整 agent 轮次结束后Gateway 步骤追踪、ACP 进度stream_delta_callback每个流式 token启用时CLI 流式展示tool_gen_callback从流中解析出工具调用时CLI spinner 中的工具预览status_callback状态变更时思考、执行等ACP 状态更新预算与回退行为迭代预算agent 通过IterationBudget追踪迭代次数默认90 次迭代可通过agent.max_turns配置每个 agent 拥有独立预算。子 agent 获得独立预算上限为delegation.max_iterations默认 50——父 agent 与子 agent 的总迭代次数可超过父 agent 的上限达到 100% 时agent 停止并返回已完成工作的摘要回退模型当主模型失败时429 限流、5xx 服务器错误、401/403 鉴权错误检查配置中的fallback_providers列表按顺序尝试每个回退 provider成功后使用新 provider 继续对话遇到 401/403 时在故障转移前尝试刷新凭据回退系统也独立覆盖辅助任务——视觉、压缩和网页提取各自拥有独立的回退链可通过auxiliary.*配置节进行配置。压缩与持久化压缩触发时机预检API 调用前对话超过模型上下文窗口的 50%Gateway 自动压缩对话超过 85%更激进在轮次之间运行压缩过程首先将内存刷写到磁盘防止数据丢失将中间对话轮次摘要为紧凑的摘要内容保留最后 N 条消息完整不变compression.protect_last_n默认20工具调用/结果消息对保持完整不拆分生成新的 session 血缘 ID压缩会创建一个子 sessionSession 持久化每轮结束后消息保存到 session 存储通过hermes_state.py使用 SQLite内存变更刷写到MEMORY.md/USER.md可通过/resume或hermes chat --resume恢复 session关键源文件文件用途run_agent.pyAIAgent 类——完整的 agent loopagent/prompt_builder.py从内存、技能、上下文文件和个性组装系统 promptagent/context_engine.pyContextEngine ABC——可插拔的上下文管理agent/context_compressor.py默认引擎——有损摘要算法agent/prompt_caching.pyAnthropic prompt 缓存标记和缓存指标agent/auxiliary_client.py用于辅助任务的辅助 LLM 客户端视觉、摘要model_tools.py工具 schema 集合handle_function_call()分发总结一下run_conversation方法源码以下是 agent/conversation_loop.py 中 run_conversation 方法的主要环节分析~4900行是 Hermes Agent 核心循环的心脏1. 前置初始化~L364–500参数与守卫- 接收 user_message、system_message、conversation_history、stream_callback 等- _install_safe_stdio() — 保护 daemon/systemd 下 stdout 写崩溃- 确保 session DB 存在运行时设置- 向辅助客户端注册主 provider/modelset_runtime_main- 设置 session 日志上下文、skill 写入来源标记- 从之前的 fallback 恢复主 runtime_restore_primary_runtime- 清理用户输入中的 surrogate 字符重置状态- 重置各类重试计数器_invalid_tool_retries、_empty_content_retries 等 10 个- 重置 iteration budget、vision 支持标记、tool guardrails连接健康检查- 检测并清理前次 provider 留下的死 TCP 连接2. 消息历史恢复与 用户消息注入~L501–600- 复制 conversation_history → messages避免修改调用者列表- 从历史中恢复 todo store 和 nudge 计数器gateway 每次创建新 AIAgent这些是内存状态- 追加用户消息记录索引用于后续插件上下文注入- 系统提示词缓存_restore_or_build_system_prompt — 从 session DB 恢复保持 Anthropic prefix cache 命中或重新构建3. 预检上下文压缩~L603–701- 模型切换后检查消息列表是否超过 context window- 如果需要则调用 _compress_context 主动压缩最多 3 轮- 压缩后重建 session、重置重试计数器4. Plugin Hook: pre_llm_call~L703–739- 插件可以返回 context 字符串注入到当前轮的 user 消息中- 注入 user message 而非 system prompt保持 prompt cache 不变5. 主循环 — Agent Loop~L814–4524这是最核心的部分每次迭代执行5.1 中断检查与预算控制~L818–838- 检查 interrupt 标记- 消耗 iteration budget耗尽时跳出5.2 Step Callback Steer Drain~L841–924- 向 gateway 回报当前步骤- 处理 /steer 指令用户中途给模型的补充指令5.3 构建 API 请求~L926–1122- 修复 tool call 参数- 修复消息序列角色交替错误- 注入内存 prefetch 结果和插件上下文到当前 user 消息- 复制 reasoning 字段- 组装最终 system prompt缓存 临时- 应用 prompt caching- 规范化 JSON、去除 surrogate- 估算 token 数5.4 API 重试循环~L1178–3567, 最复杂的部分成功路径- 调用 provider → 校验响应结构 → 提取 finish_reason → 记录 token 用量和成本截断处理length / truncation- 检测 thinking-budget 耗尽- 输出截断 → 最多 3 次 continuation 重试- tool call 截断 → 最多 3 次重试同时提升 max_tokens异常恢复巨大的 try/except- Unicode 编码错误去除 surrogate 字符 或 强制 ASCII 模式- 图片被拒切换为纯文本模式- 图片过大压缩图片后重试- Multimodal tool content 被拒降级列表类型 tool 内容- Anthropic OAuth 1M context beta 被拒禁用 beta header 后重建客户端- 认证过期刷新 OAuth 令牌Codex / Nous / Anthropic / Copilot- Thinking 签名无效清除 reasoning_details- 加密推理回放被拒禁用回放- llama.cpp 语法错误去除 regex pattern/format降级策略链按优先级1. credential_pool 轮换 API key4292. 上下文压缩413, context_overflow, long_context_tier3. 切换到 fallback 模型/提供商4. 指数退避重试5. 最终报错返回5.5 响应规范化~L3608–3636- 通过 transport 层将不同 API 模式chat_completions / anthropic_messages / bedrock_converse / codex_responses统一为标准 assistant_message 格式5.6 Post-API Plugin Hook: post_api_request~L36385.7 处理 Tool Calls~L3802–4131- 验证 tool 名称是否存在自动修复常见变形- 验证 JSON 参数是否合法- 检测截断的 JSON → 拒绝执行- Post-call guardrails限制 delegate_task 并行数、去重- 执行工具_execute_tool_calls- 执行后检查上下文大小 → 按需压缩- 增量保存 session5.8 处理无工具调用的最终响应~L4133–4467- 内容为空 → 多种恢复尝试- 使用已流送的片段partial stream recovery- 使用前一轮的工具 内容组合housekeeping fallback- 追加 nudge 提示让模型继续- Thinking-only → prefill continuation- 空响应重试最多 3 次- fallback 后再试- 最终返回 (empty)- 组装最终 assistant 消息清理临时 scaffolding- 截断片段拼接把前几轮 continuation 的文本合并5.9 外层异常捕获~L4469–4524- 填充未回答的 tool_call_id 的 error 结果- 接近 max_iterations 时跳出6. 后循环处理~L4526–49016.1 Budget 耗尽处理~L4526–4595- 若 iteration budget 耗尽且无最终回复 → 调用 _handle_max_iterations去掉 tools 后单次求和式问答- 如果是 Kanban Worker记录 timeout 到 kanban DB6.2 会话持久化~L4615–4616- 去除内部 scaffolding 消息- 保存到 JSON 日志 SQLite6.3 诊断日志~L4618–4660- 记录 _turn_exit_reason、API 调用次数、工具轮次、响应长度- 若以 tool result 结尾 → WARNING 级别6.4 文件变更验证器~L4662–4685- 检测 write_file/patch 是否真的写入成功附加 footer6.5 Turn 结束解释器~L4687–4734- 如果响应为空或过短用 _turn_exit_reason 生成可读解释6.6 Plugin Hooks~L4736–4779- transform_llm_output允许插件修改最终文本- post_llm_call允许插件持久化对话数据6.7 后台审查~L4848–4874- 检查是否需要触发 memory nudge 或 skill review- 如果需要在后台 fork 一个独立 agent 线程执行6.8 最终 Hooks 返回~L4886–4901- on_session_end hook- 返回包含 final_response、messages、token 用量、cost、reasoning 等完整结果字典总结run_conversation 本质上是一个带有完整故障恢复链的 agent 工具调用循环其核心阶段链Initialize → Build Messages → [Tool Loop] → Post-process → Return↑ │└── Retry / Compress / Fallback关键设计特点- 分层恢复credential 轮换 → 压缩 → fallback → 退避重试 → 报错- 自适应输出处理截断 continuation、thinking-aware、空响应指纹、prefill 桥接- 响应完整性保障流中断恢复、片段拼接、tool call 截断检测- 插件化4 个 hook 点不侵入核心逻辑- 可观测性每轮结束记录详尽诊断日志包含退出原因、API 调用次数、预算使用情况
hermes源码学习3-Agent Loop 内部机制
先复习一下hermes架构┌─────────────────────────────────────────────────────────────────────┐ │ Entry Points │ │ │ │ CLI (cli.py) Gateway (gateway/run.py) ACP (acp_adapter/) │ │ Batch Runner API Server Python Library │ └──────────┬──────────────┬───────────────────────┬───────────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ AIAgent (run_agent.py) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Prompt │ │ Provider │ │ Tool │ │ │ │ Builder │ │ Resolution │ │ Dispatch │ │ │ │ (prompt_ │ │ (runtime_ │ │ (model_ │ │ │ │ builder.py) │ │ provider.py)│ │ tools.py) │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌──────┴───────┐ ┌──────┴───────┐ ┌──────┴───────┐ │ │ │ Compression │ │ 3 API Modes │ │ Tool Registry│ │ │ │ Caching │ │ chat_compl. │ │ (registry.py)│ │ │ │ │ │ codex_resp. │ │ 70 tools │ │ │ │ │ │ anthropic │ │ 28 toolsets │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────┴─────────────────┴─────────────────┴───────────────────────┘ │ │ ▼ ▼ ┌───────────────────┐ ┌──────────────────────┐ │ Session Storage │ │ Tool Backends │ │ (SQLite FTS5) │ │ Terminal (7 backends) │ │ hermes_state.py │ │ Browser (5 backends) │ │ gateway/session.py│ │ Web (4 backends) │ └───────────────────┘ │ MCP (dynamic) │ │ File, Vision, etc. │ └──────────────────────┘核心编排引擎是run_agent.py中的AIAgent类——这是一个大型文件15k 行 新版本已经重构 只有5k负责处理从 prompt提示词组装到工具分发再到 provider 故障转移的所有逻辑。核心职责AIAgent负责通过prompt_builder.py组装有效的系统 prompt 和工具 schema选择正确的 provider/API 模式chat_completions、codex_responses、anthropic_messages发起支持取消操作的可中断模型调用执行工具调用顺序执行或通过线程池并发执行以 OpenAI 消息格式维护对话历史处理压缩、重试和回退模型切换跨父 agent 和子 agent 追踪迭代预算在上下文丢失前将持久化内存刷写到磁盘两个入口点# 简单接口——返回最终响应字符串 response agent.chat(Fix the bug in main.py) # 完整接口——返回包含消息、元数据、用量统计的 dict result agent.run_conversation( user_messageFix the bug in main.py, system_messageNone, # 省略时自动构建 conversation_historyNone, # 省略时自动从 session 加载 task_idtask_abc123 )chat()是对run_conversation()的轻量封装从结果 dict 中提取final_response字段。API 模式Hermes 支持三种 API 执行模式通过 provider 选择、显式参数和 base URL 启发式规则来确定API 模式用途客户端类型chat_completions兼容 OpenAI 的端点OpenRouter、自定义及大多数 provideropenai.OpenAIcodex_responsesOpenAI Codex / Responses APIopenai.OpenAI使用 Responses 格式anthropic_messages原生 Anthropic Messages API通过适配器使用anthropic.Anthropic模式决定了消息的格式化方式、工具调用的结构、响应的解析方式以及缓存/流式传输的工作方式。三种模式在 API 调用前后均收敛到相同的内部消息格式OpenAI 风格的role/content/tool_callsdict。模式解析顺序显式api_mode构造函数参数最高优先级Provider 特定检测例如anthropicprovider →anthropic_messagesBase URL 启发式规则例如api.anthropic.com→anthropic_messages默认chat_completions单轮生命周期agent loop 的每次迭代按以下顺序执行run_conversation() 1. 若未提供则生成 task_id 2. 将用户消息追加到对话历史 3. 构建或复用已缓存的系统 promptprompt_builder.py 4. 检查是否需要预检压缩上下文超过 50% 5. 从对话历史构建 API 消息 - chat_completions直接使用 OpenAI 格式 - codex_responses转换为 Responses API 输入项 - anthropic_messages通过 anthropic_adapter.py 转换 6. 注入临时 prompt 层预算警告、上下文压力提示 7. 若使用 Anthropic应用 prompt 缓存标记 8. 发起可中断的 API 调用_interruptible_api_call 9. 解析响应 - 若有 tool_calls执行工具追加结果回到步骤 5 - 若为文本响应持久化 session按需刷写内存返回消息格式所有消息在内部均使用兼容 OpenAI 的格式{role: system, content: ...} {role: user, content: ...} {role: assistant, content: ..., tool_calls: [...]} {role: tool, tool_call_id: ..., content: ...}推理内容来自支持扩展思考的模型存储在assistant_msg[reasoning]中并可选择通过reasoning_callback展示。消息交替规则agent loop 强制执行严格的消息角色交替规则系统消息之后User → Assistant → User → Assistant → ...工具调用期间Assistant含 tool_calls→ Tool → Tool → ... → Assistant不允许连续出现两条 assistant 消息不允许连续出现两条 user 消息只有tool角色可以连续出现并行工具结果Provider 会验证这些序列并拒绝格式错误的历史记录。可中断的 API 调用API 请求被封装在_interruptible_api_call()中该方法在后台线程中执行实际的 HTTP 调用同时监听中断事件┌────────────────────────────────────────────────────┐ │ 主线程 API 线程 │ │ │ │ 等待 HTTP POST │ │ - 响应就绪 ───▶ 发送至 provider │ │ - 中断事件 │ │ - 超时 │ └────────────────────────────────────────────────────┘当发生中断用户发送新消息、/stop命令或信号时API 线程被放弃响应被丢弃agent 可以处理新输入或干净地关闭不会将部分响应注入对话历史工具执行顺序执行与并发执行当模型返回工具调用时单个工具调用→ 直接在主线程中执行多个工具调用→ 通过ThreadPoolExecutor并发执行例外标记为交互式的工具如clarify强制顺序执行无论完成顺序如何结果均按原始工具调用顺序重新插入执行流程for each tool_call in response.tool_calls: 1. 从 tools/registry.py 解析处理器 2. 触发 pre_tool_call 插件 hook 3. 检查是否为危险命令tools/approval.py - 若危险调用 approval_callback等待用户确认 4. 使用参数 task_id 执行处理器 5. 触发 post_tool_call 插件 hook 6. 将 {role: tool, content: result} 追加到历史Agent 级工具部分工具在到达handle_function_call()之前由run_agent.py提前拦截工具拦截原因todo读写 agent 本地任务状态memory向持久化内存文件写入内容有字符限制session_search通过 agent 的 session DB 查询 session 历史delegate_task以隔离上下文生成子 agent这些工具直接修改 agent 状态并返回合成的工具结果不经过注册表。回调接口AIAgent支持平台特定的回调用于在 CLI、gateway 和 ACP 集成中实现实时进度展示回调触发时机使用方tool_progress_callback每次工具执行前后CLI spinner、gateway 进度消息thinking_callback模型开始/停止思考时CLI thinking... 指示器reasoning_callback模型返回推理内容时CLI 推理展示、gateway 推理块clarify_callback调用clarify工具时CLI 输入提示、gateway 交互消息step_callback每次完整 agent 轮次结束后Gateway 步骤追踪、ACP 进度stream_delta_callback每个流式 token启用时CLI 流式展示tool_gen_callback从流中解析出工具调用时CLI spinner 中的工具预览status_callback状态变更时思考、执行等ACP 状态更新预算与回退行为迭代预算agent 通过IterationBudget追踪迭代次数默认90 次迭代可通过agent.max_turns配置每个 agent 拥有独立预算。子 agent 获得独立预算上限为delegation.max_iterations默认 50——父 agent 与子 agent 的总迭代次数可超过父 agent 的上限达到 100% 时agent 停止并返回已完成工作的摘要回退模型当主模型失败时429 限流、5xx 服务器错误、401/403 鉴权错误检查配置中的fallback_providers列表按顺序尝试每个回退 provider成功后使用新 provider 继续对话遇到 401/403 时在故障转移前尝试刷新凭据回退系统也独立覆盖辅助任务——视觉、压缩和网页提取各自拥有独立的回退链可通过auxiliary.*配置节进行配置。压缩与持久化压缩触发时机预检API 调用前对话超过模型上下文窗口的 50%Gateway 自动压缩对话超过 85%更激进在轮次之间运行压缩过程首先将内存刷写到磁盘防止数据丢失将中间对话轮次摘要为紧凑的摘要内容保留最后 N 条消息完整不变compression.protect_last_n默认20工具调用/结果消息对保持完整不拆分生成新的 session 血缘 ID压缩会创建一个子 sessionSession 持久化每轮结束后消息保存到 session 存储通过hermes_state.py使用 SQLite内存变更刷写到MEMORY.md/USER.md可通过/resume或hermes chat --resume恢复 session关键源文件文件用途run_agent.pyAIAgent 类——完整的 agent loopagent/prompt_builder.py从内存、技能、上下文文件和个性组装系统 promptagent/context_engine.pyContextEngine ABC——可插拔的上下文管理agent/context_compressor.py默认引擎——有损摘要算法agent/prompt_caching.pyAnthropic prompt 缓存标记和缓存指标agent/auxiliary_client.py用于辅助任务的辅助 LLM 客户端视觉、摘要model_tools.py工具 schema 集合handle_function_call()分发总结一下run_conversation方法源码以下是 agent/conversation_loop.py 中 run_conversation 方法的主要环节分析~4900行是 Hermes Agent 核心循环的心脏1. 前置初始化~L364–500参数与守卫- 接收 user_message、system_message、conversation_history、stream_callback 等- _install_safe_stdio() — 保护 daemon/systemd 下 stdout 写崩溃- 确保 session DB 存在运行时设置- 向辅助客户端注册主 provider/modelset_runtime_main- 设置 session 日志上下文、skill 写入来源标记- 从之前的 fallback 恢复主 runtime_restore_primary_runtime- 清理用户输入中的 surrogate 字符重置状态- 重置各类重试计数器_invalid_tool_retries、_empty_content_retries 等 10 个- 重置 iteration budget、vision 支持标记、tool guardrails连接健康检查- 检测并清理前次 provider 留下的死 TCP 连接2. 消息历史恢复与 用户消息注入~L501–600- 复制 conversation_history → messages避免修改调用者列表- 从历史中恢复 todo store 和 nudge 计数器gateway 每次创建新 AIAgent这些是内存状态- 追加用户消息记录索引用于后续插件上下文注入- 系统提示词缓存_restore_or_build_system_prompt — 从 session DB 恢复保持 Anthropic prefix cache 命中或重新构建3. 预检上下文压缩~L603–701- 模型切换后检查消息列表是否超过 context window- 如果需要则调用 _compress_context 主动压缩最多 3 轮- 压缩后重建 session、重置重试计数器4. Plugin Hook: pre_llm_call~L703–739- 插件可以返回 context 字符串注入到当前轮的 user 消息中- 注入 user message 而非 system prompt保持 prompt cache 不变5. 主循环 — Agent Loop~L814–4524这是最核心的部分每次迭代执行5.1 中断检查与预算控制~L818–838- 检查 interrupt 标记- 消耗 iteration budget耗尽时跳出5.2 Step Callback Steer Drain~L841–924- 向 gateway 回报当前步骤- 处理 /steer 指令用户中途给模型的补充指令5.3 构建 API 请求~L926–1122- 修复 tool call 参数- 修复消息序列角色交替错误- 注入内存 prefetch 结果和插件上下文到当前 user 消息- 复制 reasoning 字段- 组装最终 system prompt缓存 临时- 应用 prompt caching- 规范化 JSON、去除 surrogate- 估算 token 数5.4 API 重试循环~L1178–3567, 最复杂的部分成功路径- 调用 provider → 校验响应结构 → 提取 finish_reason → 记录 token 用量和成本截断处理length / truncation- 检测 thinking-budget 耗尽- 输出截断 → 最多 3 次 continuation 重试- tool call 截断 → 最多 3 次重试同时提升 max_tokens异常恢复巨大的 try/except- Unicode 编码错误去除 surrogate 字符 或 强制 ASCII 模式- 图片被拒切换为纯文本模式- 图片过大压缩图片后重试- Multimodal tool content 被拒降级列表类型 tool 内容- Anthropic OAuth 1M context beta 被拒禁用 beta header 后重建客户端- 认证过期刷新 OAuth 令牌Codex / Nous / Anthropic / Copilot- Thinking 签名无效清除 reasoning_details- 加密推理回放被拒禁用回放- llama.cpp 语法错误去除 regex pattern/format降级策略链按优先级1. credential_pool 轮换 API key4292. 上下文压缩413, context_overflow, long_context_tier3. 切换到 fallback 模型/提供商4. 指数退避重试5. 最终报错返回5.5 响应规范化~L3608–3636- 通过 transport 层将不同 API 模式chat_completions / anthropic_messages / bedrock_converse / codex_responses统一为标准 assistant_message 格式5.6 Post-API Plugin Hook: post_api_request~L36385.7 处理 Tool Calls~L3802–4131- 验证 tool 名称是否存在自动修复常见变形- 验证 JSON 参数是否合法- 检测截断的 JSON → 拒绝执行- Post-call guardrails限制 delegate_task 并行数、去重- 执行工具_execute_tool_calls- 执行后检查上下文大小 → 按需压缩- 增量保存 session5.8 处理无工具调用的最终响应~L4133–4467- 内容为空 → 多种恢复尝试- 使用已流送的片段partial stream recovery- 使用前一轮的工具 内容组合housekeeping fallback- 追加 nudge 提示让模型继续- Thinking-only → prefill continuation- 空响应重试最多 3 次- fallback 后再试- 最终返回 (empty)- 组装最终 assistant 消息清理临时 scaffolding- 截断片段拼接把前几轮 continuation 的文本合并5.9 外层异常捕获~L4469–4524- 填充未回答的 tool_call_id 的 error 结果- 接近 max_iterations 时跳出6. 后循环处理~L4526–49016.1 Budget 耗尽处理~L4526–4595- 若 iteration budget 耗尽且无最终回复 → 调用 _handle_max_iterations去掉 tools 后单次求和式问答- 如果是 Kanban Worker记录 timeout 到 kanban DB6.2 会话持久化~L4615–4616- 去除内部 scaffolding 消息- 保存到 JSON 日志 SQLite6.3 诊断日志~L4618–4660- 记录 _turn_exit_reason、API 调用次数、工具轮次、响应长度- 若以 tool result 结尾 → WARNING 级别6.4 文件变更验证器~L4662–4685- 检测 write_file/patch 是否真的写入成功附加 footer6.5 Turn 结束解释器~L4687–4734- 如果响应为空或过短用 _turn_exit_reason 生成可读解释6.6 Plugin Hooks~L4736–4779- transform_llm_output允许插件修改最终文本- post_llm_call允许插件持久化对话数据6.7 后台审查~L4848–4874- 检查是否需要触发 memory nudge 或 skill review- 如果需要在后台 fork 一个独立 agent 线程执行6.8 最终 Hooks 返回~L4886–4901- on_session_end hook- 返回包含 final_response、messages、token 用量、cost、reasoning 等完整结果字典总结run_conversation 本质上是一个带有完整故障恢复链的 agent 工具调用循环其核心阶段链Initialize → Build Messages → [Tool Loop] → Post-process → Return↑ │└── Retry / Compress / Fallback关键设计特点- 分层恢复credential 轮换 → 压缩 → fallback → 退避重试 → 报错- 自适应输出处理截断 continuation、thinking-aware、空响应指纹、prefill 桥接- 响应完整性保障流中断恢复、片段拼接、tool call 截断检测- 插件化4 个 hook 点不侵入核心逻辑- 可观测性每轮结束记录详尽诊断日志包含退出原因、API 调用次数、预算使用情况