sidebar_position: 3 title: "Agent Loop Internals" description: "Detailed walkthrough of AIAgent execution, API modes, tools, callbacks, and fallback behavior" lang: ru
Внутреннее устройство цикла агента
Основным механизмом оркестровки является класс AIAgent run_agent.py — примерно 13 700 строк, которые обрабатывают все: от быстрой сборки до отправки инструментов и переключения провайдера при сбое.
Основные обязанности
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, определяемые выбором поставщика, явными аргументами и эвристикой базового URL-адреса:
| Режим API | Используется для | Тип клиента |
|---|---|---|
chat_completions |
Конечные точки, совместимые с OpenAI (OpenRouter, пользовательские, большинство поставщиков) | openai.OpenAI |
codex_responses |
Кодекс OpenAI/API ответов | openai.OpenAI с форматом ответов |
anthropic_messages |
API собственных антропных сообщений | anthropic.Anthropic через адаптер |
Режим определяет, как форматируются сообщения, как структурируются вызовы инструментов, как анализируются ответы и как работает кэширование/потоковая передача. Все три используют один и тот же внутренний формат сообщений (диктанты role/content/tool_calls в стиле OpenAI) до и после вызовов API.
Порядок разрешения режимов:
1. Явный аргумент конструктора api_mode (наивысший приоритет).
2. Обнаружение конкретного поставщика (например, поставщик anthropic → 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может иметь последовательные записи (параллельные результаты инструмента)
Поставщики проверяют эти последовательности и отклоняют искаженные истории.
Прерываемые вызовы 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
Инструменты уровня агента
Некоторые инструменты перехватываются run_agent.py до достижения handle_function_call():
| Инструмент | Почему перехвачен |
|---|---|
todo |
Чтение/запись состояния локальной задачи агента |
memory |
Записывает в файлы постоянной памяти с ограничениями по количеству символов |
session_search |
Запрашивает историю сессий через БД сессий агента |
delegate_task |
Создает субагент(ы) с изолированным контекстом |
Эти инструменты напрямую изменяют состояние агента и возвращают синтетические результаты, минуя реестр.
Поверхности обратного вызова
AIAgent поддерживает обратные вызовы для конкретной платформы, которые обеспечивают прогресс в интеграции CLI, шлюза и ACP в режиме реального времени:
| Обратный звонок | При увольнении | Используется |
|---|---|---|
tool_progress_callback |
До/после каждого применения инструмента | Счетчик CLI, сообщения о ходе работы шлюза |
thinking_callback |
Когда модель начинает/перестает думать | Индикатор CLI «думаю...» |
reasoning_callback |
Когда модель возвращает содержание рассуждений | Отображение рассуждений CLI, блоки рассуждений шлюза |
clarify_callback |
При вызове инструмента clarify |
Приглашение ввода CLI, интерактивное сообщение шлюза |
step_callback |
После каждого полного хода агента | Отслеживание шагов шлюза, прогресс ACP |
stream_delta_callback |
Каждый токен потоковой передачи (если включен) | Потоковое отображение CLI |
tool_gen_callback |
Когда вызов инструмента анализируется из потока | Предварительный просмотр инструмента CLI в Spinner |
status_callback |
Изменения состояния (мышление, выполнение и т.д.) | Обновления статуса ACP |
Бюджет и резервное поведение
Бюджет итерации
Агент отслеживает итерации через IterationBudget:
- По умолчанию: 90 итераций (настраивается через
agent.max_turns) - Каждый агент получает свой бюджет. Субагенты получают независимые бюджеты, ограниченные
delegation.max_iterations(по умолчанию 50) — общее количество итераций между родительским и субагентами может превышать ограничение родительского объекта. - При 100% агент останавливается и возвращает сводку о проделанной работе.
Резервная модель
При сбое основной модели (ограничение скорости 429, ошибка сервера 5xx, ошибка аутентификации 401/403):
- Проверьте список
fallback_providersв конфигурации. - Попробуйте каждый вариант по порядку
- В случае успеха продолжайте разговор с новым провайдером.
- В 401/403 попытайтесь обновить учетные данные перед аварийным переключением.
Резервная система также независимо выполняет вспомогательные задачи — просмотр, сжатие, веб-извлечение и поиск сеансов имеют собственную резервную цепочку, настраиваемую через раздел конфигурации auxiliary.*.
Сжатие и сохранение
Когда срабатывает сжатие
- Предварительная проверка (перед вызовом API): если разговор превышает 50 % контекстного окна модели.
- Автоматическое сжатие шлюза: если разговор превышает 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 |
ContextEngine ABC — подключаемое управление контекстом |
agent/context_compressor.py |
Механизм по умолчанию — алгоритм суммирования с потерями |
agent/prompt_caching.py |
Антропные маркеры оперативного кэширования и метрики кэширования |
agent/auxiliary_client.py |
Вспомогательный LLM-клиент для побочных задач (видение, обобщение) |
model_tools.py |
Коллекция схем инструментов, отправка handle_function_call() |