Внутреннее устройство цикла агента
Основной оркестрационный движок — класс 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 stringresponse = agent.chat("Fix the bug in main.py")
# Full interface — returns dict with messages, metadata, usage statsresult = 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.py6. Внедрить эфемерные слои prompt (предупреждения о бюджете, давление контекста)7. Применить маркеры кэширования prompt, если используется Anthropic8. Вызвать прерываемый 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
Заголовок раздела «Прерываемые вызовы API»Запросы 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():»| Инструмент | Причина перехвата |
|---|---|
todo | Reads/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 — ошибка аутентификации):»- Проверить список
fallback_providersв конфигурации - Попробовать каждый резервный вариант по порядку
- При успехе продолжить разговор с новым провайдером
- При 401/403 попытаться обновить учётные данные перед переключением на резервный провайдерСистема резервирования также охватывает вспомогательные задачи независимо — для визуального распознавания, сжатия, извлечения данных из веба и поиска по сессиям предусмотрена собственная цепочка резервирования, настраиваемая через секцию конфигурации
auxiliary.*.
Сжатие и сохранение
Заголовок раздела «Сжатие и сохранение»Когда срабатывает сжатие
Заголовок раздела «Когда срабатывает сжатие»- Предварительная проверка (перед вызовом API): если разговор превышает 50% контекстного окна модели
- Автоматическое сжатие gateway: если разговор превышает 85% (более агрессивное, выполняется между ходами)
Что происходит во время сжатия1. Память сначала сбрасывается на диск (предотвращая потерю данных)
Заголовок раздела «Что происходит во время сжатия1. Память сначала сбрасывается на диск (предотвращая потерю данных)»- Средние реплики разговора сжимаются в компактное резюме
- Последние N сообщений сохраняются без изменений (
compression.protect_last_n, по умолчанию: 20) - Пары сообщений инструмента call/result сохраняются вместе (никогда не разделяются)
- Генерируется новый идентификатор родословной сессии (сжатие создаёт «дочернюю» сессию)
Сохранение сессийПосле каждого хода:
Заголовок раздела «Сохранение сессийПосле каждого хода:»- Сообщения сохраняются в хранилище сессий (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() |