Loop
На этой странице
Основной оркестрационный движок — класс AIAgent из run_agent.py — примерно 13 700 строк, отвечающих за всё: от сборки промптов до диспетчеризации инструментов и переключения провайдеров.
Основные обязанности¶
AIAgent отвечает за:
- Сборку эффективного системного промпта и схем инструментов через
prompt_builder.py - Выбор правильного провайдера/режима API (
chat_completions,codex_responses,anthropic_messages) - Прерываемые вызовы модели с поддержкой отмены
- Выполнение вызовов инструментов (последовательно или параллельно через пул потоков)
- Ведение истории диалога в формате сообщений OpenAI
- Обработку сжатия, повторных попыток и переключения на запасную модель
- Отслеживание лимита итераций для родительских и дочерних агентов
- Сохранение персистентной памяти перед потерей контекста
Две точки входа¶
# Простой интерфейс — возвращает итоговую строку ответа
response = agent.chat("Fix the bug in main.py")
# Полный интерфейс — возвращает словарь с сообщениями, метаданными и статистикой использования
result = agent.run_conversation(
user_message="Fix the bug in main.py",
system_message=None, # собирается автоматически, если опущен
conversation_history=None, # автоматически загружается из сессии, если опущена
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 через адаптер |
Режим определяет, как форматируются сообщения, как структурируются вызовы инструментов, как парсятся ответы и как работают кэширование/стриминг. Все три режима сходятся к одному и тому же внутреннему формату сообщений (словари в стиле OpenAI с role/content/tool_calls) до и после вызовов API.
Порядок определения режима:
- Явный аргумент конструктора
api_mode(наивысший приоритет) - Определение по провайдеру (например, провайдер
anthropic→anthropic_messages) - Эвристики по базовому URL (например,
api.anthropic.com→anthropic_messages) - По умолчанию:
chat_completions
Жизненный цикл шага¶
Каждая итерация цикла агента следует такой последовательности:
run_conversation()
1. Генерация task_id, если не предоставлен
2. Добавление сообщения пользователя в историю диалога
3. Сборка или повторное использование кэшированного системного промпта (prompt_builder.py)
4. Проверка необходимости сжатия перед запросом (контекст > 50%)
5. Формирование API-сообщений из истории диалога
- chat_completions: формат OpenAI как есть
- codex_responses: преобразование во входные элементы Responses API
- anthropic_messages: преобразование через anthropic_adapter.py
6. Внедрение эфемерных слоёв промпта (предупреждения о лимитах, давление контекста)
7. Применение маркеров кэширования промпта (если 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.
Правила чередования сообщений¶
Цикл агента обеспечивает строгое чередование ролей сообщений:
- После системного сообщения:
Пользователь → Ассистент → Пользователь → Ассистент → ... - Во время вызова инструментов:
Ассистент (с tool_calls) → Инструмент → Инструмент → ... → Ассистент - Никогда два сообщения ассистента подряд
- Никогда два сообщения пользователя подряд
- Только роль
toolможет иметь последовательные записи (результаты параллельных инструментов)
Провайдеры проверяют эти последовательности и могут отклонить некорректную историю.
Прерываемые вызовы API¶
API-запросы обёрнуты в _interruptible_api_call(), которая запускает фактический HTTP-запрос в фоновом потоке, одновременно отслеживая событие прерывания:
┌────────────────────────────────────────────────────┐
│ Главный поток Поток API │
│ │
│ Ожидание: HTTP POST │
│ - готовности ответа ───▶ к провайдеру │
│ - события прерывания │
│ - тайм-аута │
└────────────────────────────────────────────────────┘
При прерывании (пользователь отправил новое сообщение, команда /stop или сигнал):
- Поток API завершается (ответ отбрасывается)
- Агент может обработать новый ввод или завершить работу корректно
- Частичный ответ не вставляется в историю диалога
Выполнение инструментов¶
Последовательное vs параллельное¶
Когда модель возвращает вызовы инструментов:
- Одиночный вызов инструмента → выполняется непосредственно в главном потоке
- Несколько вызовов инструментов → выполняются параллельно через
ThreadPoolExecutor - Исключение: инструменты, помеченные как интерактивные (например,
clarify), принудительно выполняются последовательно - Результаты вставляются в исходном порядке вызовов инструментов независимо от порядка завершения
Поток выполнения¶
for each tool_call in response.tool_calls:
1. Определить обработчик из tools/registry.py
2. Запустить хук плагина pre_tool_call
3. Проверить, является ли команда опасной (tools/approval.py)
- Если опасная: вызвать approval_callback, ожидать пользователя
4. Выполнить обработчик с аргументами + task_id
5. Запустить хук плагина post_tool_call
6. Добавить {"role": "tool", "content": result} в историю
Инструменты уровня агента¶
Некоторые инструменты перехватываются run_agent.py до того, как они достигают handle_function_call():
| Инструмент | Почему перехватывается |
|---|---|
todo |
Чтение/запись локального состояния задач агента |
memory |
Запись в файлы персистентной памяти с ограничениями по символам |
session_search |
Поиск по истории сессий через БД сессий агента |
delegate_task |
Создание подчинённых агентов с изолированным контекстом |
Эти инструменты изменяют состояние агента напрямую и возвращают синтетические результаты инструментов, минуя реестр.
Поверхности обратных вызовов¶
AIAgent поддерживает обратные вызовы для конкретных платформ, обеспечивающие отображение прогресса в реальном времени в CLI, gateway и ACP-интеграциях:
| Callback | Когда вызывается | Используется |
|---|---|---|
tool_progress_callback |
До/после каждого выполнения инструмента | Спиннер CLI, сообщения о прогрессе в gateway |
thinking_callback |
Когда модель начинает/заканчивает размышление | Индикатор "thinking..." в CLI |
reasoning_callback |
Когда модель возвращает содержимое рассуждений | Отображение рассуждений в CLI, блоки рассуждений в gateway |
clarify_callback |
Когда вызывается инструмент clarify |
Запрос ввода в CLI, интерактивное сообщение в 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 — ошибка аутентификации):
- Проверяется список
fallback_providersв конфигурации - Каждая запасная модель пробуется по порядку
- При успехе разговор продолжается с новым провайдером
- При ошибках 401/403 выполняется попытка обновления учётных данных перед переключением
Система запасных моделей также независимо покрывает вспомогательные задачи — для vision, сжатия, веб-извлечения и поиска по сессиям существует своя цепочка запасных моделей, настраиваемая через секцию конфигурации auxiliary.*.
Сжатие и сохранение состояния¶
Когда срабатывает сжатие¶
- Предварительное (перед вызовом API): Если диалог превышает 50% контекстного окна модели
- Авто-сжатие в gateway: Если диалог превышает 85% (более агрессивно, выполняется между шагами)
Что происходит во время сжатия¶
- Память сначала сохраняется на диск (предотвращение потери данных)
- Средние шаги диалога обобщаются в компактную сводку
- Последние N сообщений сохраняются нетронутыми (
compression.protect_last_n, по умолчанию: 20) - Пары сообщений вызов инструмента/результат сохраняются вместе (никогда не разделяются)
- Генерируется новый идентификатор линии сессии (сжатие создаёт «дочернюю» сессию)
Сохранение сессии¶
После каждого шага:
- Сообщения сохраняются в хранилище сессий (SQLite через
hermes_state.py) - Изменения в памяти сбрасываются в
MEMORY.md/USER.md - Сессию можно возобновить позже через
/resumeилиhermes chat --resume
Ключевые исходные файлы¶
| Файл | Назначение |
|---|---|
run_agent.py |
Класс AIAgent — полный цикл агента (~13 700 строк) |
agent/prompt_builder.py |
Сборка системного промпта из памяти, навыков, контекстных файлов, личности |
agent/context_engine.py |
ABC ContextEngine — подключаемое управление контекстом |
agent/context_compressor.py |
Движок по умолчанию — алгоритм сжатия с потерями |
agent/prompt_caching.py |
Маркеры кэширования промптов Anthropic и метрики кэша |
agent/auxiliary_client.py |
Вспомогательный LLM-клиент для побочных задач (vision, суммаризация) |
model_tools.py |
Сбор схем инструментов, диспетчеризация handle_function_call() |
Связанные документы¶
- Разрешение провайдера во время выполнения
- Сборка промпта
- Сжатие контекста и кэширование промптов
- Выполнение инструментов
- Две точки входа
- Режимы API
- Жизненный цикл шага
- Формат сообщений
- Правила чередования сообщений
- Прерываемые вызовы API
- Выполнение инструментов
- Последовательное vs параллельное
- Поток выполнения
- Инструменты уровня агента
- Поверхности обратных вызовов
- Лимиты и запасное поведение
- Лимит итераций
- Запасная модель
- Сжатие и сохранение состояния
- Когда срабатывает сжатие
- Что происходит во время сжатия
- Сохранение сессии
- Ключевые исходные файлы
- Связанные документы