Перейти к содержимому

Внутреннее устройство цикла агента

Основной оркестрационный движок — класс AIAgent из run_agent.py — около 13 700 строк кода, которые управляют всем, от сборки prompt до отправки инструментов и переключения между провайдерами.

AIAgent отвечает за:

  • Сборка эффективного system prompt и схем инструментов через 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, определяемые на основе выбора провайдера, явных аргументов и базовых эвристик URL:| API режим | Используется для | Тип клиента |
|----------|----------|-------------|
| `chat_completions` | Совместимые с OpenAI эндпоинты (OpenRouter, пользовательские, большинство провайдеров) | `openai.OpenAI` |
| `codex_responses` | OpenAI Codex / Responses API | `openai.OpenAI` с форматом Responses |
| `anthropic_messages` | Нативные Anthropic Messages API | `anthropic.Anthropic` через адаптер |Режим определяет, как форматируются сообщения, как структуруются вызовы инструментов, как разбираются ответы и как работает caching/streaming. Все три сходятся к единому внутреннему формату сообщений (словари в стиле OpenAI: `role`/`content`/`tool_calls`) до и после вызовов API.**Порядок определения режима:**
1. Явный аргумент конструктора `api_mode` (наивысший приоритет)
2. Обнаружение для конкретного провайдера (например, провайдер `anthropic``anthropic_messages`)
3. Эвристики базового URL (например, `api.anthropic.com``anthropic_messages`)
4. По умолчанию: `chat_completions`
## Жизненный цикл хода
Каждая итерация цикла агента следует этой последовательности:run_conversation()
1. Сгенерировать task_id, если не предоставлен
2. Добавить сообщение пользователя в историю разговора
3. Собрать или переиспользовать кэшированный системный prompt (prompt_builder.py)
4. Проверить, нужна ли предварительная компрессия (>50% контекста)
5. Собрать API сообщения из истории разговора
- chat_completions: формат OpenAI как есть
- codex_responses: конвертировать в API входные элементы Responses- anthropic_messages: конвертировать через anthropic_adapter.py
6. Внедрить эфемерные слои prompt (предупреждения о бюджете, давление контекста)
7. Применить маркеры кэширования prompt, если используется Anthropic
8. Вызвать прерываемый API вызов (_interruptible_api_call)
9. Разобрать ответ:
- Если tool_calls: выполнить их, добавить результаты, вернуться к шагу 5
- Если текстовый ответ: сохранить сессию, при необходимости сбросить память, вернуть результат

Все сообщения используют формат, совместимый с 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 может содержать последовательные записи (параллельные результаты инструментов)Провайдеры проверяют эти последовательности и отклоняют некорректные истории.

Запросы API оборачиваются в _interruptible_api_call(), который выполняет фактический вызов HTTP в фоновом потоке, одновременно отслеживая событие прерывания:┌────────────────────────────────────────────────────┐ │ Основной поток API поток │ │ │ │ ожидание: HTTP POST │ │ - ответ готов ───▶ к провайдеру │ │ - событие прерывания │ │ - таймаут │ └────────────────────────────────────────────────────┘```При прерывании (пользователь отправляет новое сообщение, команда /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

Инструменты уровня агентаНекоторые инструменты перехватываются run_agent.py до обращения к handle_function_call():

Заголовок раздела «Инструменты уровня агентаНекоторые инструменты перехватываются run_agent.py до обращения к handle_function_call():»
ИнструментПричина перехвата
todoReads/writes локальное состояние задач агента
memoryЗаписывает в постоянные файлы памяти с ограничениями по размеру
session_searchЗапрашивает историю сессий через базу данных сессий агента
delegate_taskЗапускает субагентов с изолированным контекстом

AIAgent поддерживает платформенно-специфичные обратные вызовы, которые обеспечивают отображение прогресса в реальном времени в CLI, gateway и интеграциях ACP:| Callback | Когда срабатывает | Используется | |----------|-----------|---------| | tool_progress_callback | Before/after каждого выполнения инструмента | CLI спиннер, сообщения о прогрессе gateway | | thinking_callback | Когда модель starts/stops размышляет | CLI индикатор “думаю…” | | reasoning_callback | Когда модель возвращает содержимое рассуждений | CLI отображение рассуждений, блоки рассуждений gateway || clarify_callback | Когда вызывается инструмент clarify | CLI входной prompt, интерактивное сообщение gateway | | step_callback | После каждого полного хода агента | Отслеживание шагов gateway, ACP прогресс | | stream_delta_callback | Каждый потоковый токен (когда включено) | CLI потоковое отображение | | tool_gen_callback | Когда вызов инструмента разобран из потока | CLI предпросмотр инструмента в спиннере || status_callback | Изменения состояния (размышление, выполнение и т.д.) | ACP обновления статуса |

Агент отслеживает итерации через IterationBudget:

  • По умолчанию: 90 итераций (настраивается через agent.max_turns)
  • Каждый агент получает собственный бюджет. Подагенты получают независимые бюджеты, ограниченные значением delegation.max_iterations (по умолчанию 50) — общее количество итераций по родительскому агенту и подагентам может превышать лимит родительского агента
  • При достижении 100% агент останавливается и возвращает сводку выполненной работы

Резервная модельКогда основная модель возвращает ошибку (429 — превышение лимита запросов, 5xx — ошибка сервера, 401/403 — ошибка аутентификации):

Заголовок раздела «Резервная модельКогда основная модель возвращает ошибку (429 — превышение лимита запросов, 5xx — ошибка сервера, 401/403 — ошибка аутентификации):»
  1. Проверить список fallback_providers в конфигурации
  2. Попробовать каждый резервный вариант по порядку
  3. При успехе продолжить разговор с новым провайдером
  4. При 401/403 попытаться обновить учётные данные перед переключением на резервный провайдерСистема резервирования также охватывает вспомогательные задачи независимо — для визуального распознавания, сжатия, извлечения данных из веба и поиска по сессиям предусмотрена собственная цепочка резервирования, настраиваемая через секцию конфигурации auxiliary.*.
  • Предварительная проверка (перед вызовом API): если разговор превышает 50% контекстного окна модели
  • Автоматическое сжатие gateway: если разговор превышает 85% (более агрессивное, выполняется между ходами)

Что происходит во время сжатия1. Память сначала сбрасывается на диск (предотвращая потерю данных)

Заголовок раздела «Что происходит во время сжатия1. Память сначала сбрасывается на диск (предотвращая потерю данных)»
  1. Средние реплики разговора сжимаются в компактное резюме
  2. Последние N сообщений сохраняются без изменений (compression.protect_last_n, по умолчанию: 20)
  3. Пары сообщений инструмента call/result сохраняются вместе (никогда не разделяются)
  4. Генерируется новый идентификатор родословной сессии (сжатие создаёт «дочернюю» сессию)
  • Сообщения сохраняются в хранилище сессий (SQLite через hermes_state.py)
  • Изменения памяти сбрасываются в MEMORY.md / USER.md
  • Сессию можно возобновить позже через /resume или hermes chat --resume

Ключевые исходные файлы| Файл | Назначение |

Заголовок раздела «Ключевые исходные файлы| Файл | Назначение |»

|------|---------| | run_agent.py | Класс AIAgent — полный цикл агента (~13 700 строк) | | agent/prompt_builder.py | Сборка системного prompt из памяти, навыков, контекстных файлов, персональности | | agent/context_engine.py | ContextEngine ABC — подключаемое управление контекстом | | agent/context_compressor.py | Движок по умолчанию — алгоритм сжатия с потерями || agent/prompt_caching.py | Маркеры кэширования промптов Anthropic и метрики кэша | | agent/auxiliary_client.py | Вспомогательный клиент LLM для побочных задач (визуализация, суммаризация) | | model_tools.py | Сбор схем инструментов, диспетчеризация handle_function_call() |