Внутреннее устройство цикла агента
Основным механизмом оркестрации является класс AIAgent из файла run_agent.py — большой файл, который обрабатывает всё: от сборки промптов до диспетчеризации инструментов и переключения провайдеров при сбоях.
Основные обязанности
AIAgent отвечает за:
- Сборку эффективного системного промпта и схем инструментов с помощью
prompt_builder.py - Выбор правильного провайдера/режима API (
chat_completions,codex_responses,anthropic_messages) - Выполнение прерываемых вызовов модели с поддержкой отмены
- Исполнение вызовов инструментов (последовательно или параллельно через пул потоков)
- Ведение истории диалога в формате сообщений OpenAI
- Обработку сжатия, повторных попыток и переключения на резервную модель
- Отслеживание бюджета итераций для родительских и дочерних агентов
- Сохранение постоянной памяти перед потерей контекста
Две точки входа
# Простой интерфейс — возвращает итоговую строку ответа
response = agent.chat("Исправь ошибку в main.py")
# Полный интерфейс — возвращает словарь с сообщениями, метаданными, статистикой использования
result = agent.run_conversation(
user_message="Исправь ошибку в main.py",
system_message=None, # создаётся автоматически, если опущено
conversation_history=None, # загружается из сессии, если опущено
task_id="task_abc123"
)
chat() — это тонкая обёртка вокруг run_conversation(), которая извлекает поле final_response из результирующего словаря.
Режимы API
VibeOS поддерживает три режима выполнения 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 завершается (ответ отбрасывается)
- Агент может обработать новый ввод или корректно завершить работу
- Частичный ответ не вставляется в историю диалога
Выполнение инструментов
Последовательное и параллельное выполнение
Когда модель возвращает вызовы инструментов:
- Одиночный вызов инструмента → выполняется непосредственно в основном потоке
- Несколько вызовов инструментов → выполняются параллельно через
ThreadPoolExecutor- Исключение: инструменты, помеченные как интерактивные (например,
clarify), принудительно выполняются последовательно - Результаты вставляются в исходном порядке вызовов инструментов независимо от порядка завершения
- Исключение: инструменты, помеченные как интерактивные (например,
Поток выполнения
для каждого tool_call в 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": результат} в историю
Инструменты уровня агента
Некоторые инструменты перехватываются 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 |
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 через
vibeos_state.py) - Изменения памяти сохраняются в
MEMORY.md/USER.md - Сессию можно возобновить позже через
/resumeилиvibeos chat --resume
Ключевые исходные файлы
| Файл | Назначение |
|---|---|
run_agent.py | Класс AIAgent — полный цикл агента |
agent/prompt_builder.py | Сборка системного промпта из памяти, навыков, файлов контекста, личности |
agent/context_engine.py | ABC ContextEngine — подключаемое управление контекстом |
agent/context_compressor.py | Движок по умолчанию — алгоритм сжатия с потерями |
agent/prompt_caching.py | Маркеры кэширования промпта Anthropic и метрики кэша |
agent/auxiliary_client.py | Вспомогательный LLM-клиент для побочных задач (зрение, суммаризация) |
model_tools.py | Коллекция схем инструментов, диспетчеризация handle_function_call() |