Делегирование подзадач (Subagent Delegation)

Инструмент delegate_task порождает дочерние экземпляры AIAgent с изолированным контекстом, ограниченным набором инструментов (toolsets) и собственными терминальными сессиями. Каждый дочерний агент получает новый диалог и работает независимо — только его итоговая сводка попадает в контекст родительского агента.

Одиночная задача

delegate_task(
    goal="Debug why tests fail",
    context="Error: assertion in test_foo.py line 42",
    toolsets=["terminal", "file"]
)

Пакетный параллельный запуск

До 3 одновременных subagent по умолчанию (настраивается, без жёсткого ограничения):

delegate_task(tasks=[
    {"goal": "Research topic A", "toolsets": ["web"]},
    {"goal": "Research topic B", "toolsets": ["web"]},
    {"goal": "Fix the build", "toolsets": ["terminal", "file"]}
])

Как работает контекст subagent

Важно: Subagent ничего не знают

Subagent начинают с полностью нового диалога. Они не имеют никакого представления об истории разговора родителя, предыдущих вызовах инструментов или чём-либо, обсуждавшемся до делегирования. Единственный контекст subagent — это поля goal и context, которые родительский агент заполняет при вызове delegate_task.

Это означает, что родительский агент должен передать всё, что нужно subagent, в самом вызове:

# ПЛОХО — subagent понятия не имеет, что такое "ошибка"
delegate_task(goal="Fix the error")

# ХОРОШО — subagent имеет весь необходимый контекст
delegate_task(
    goal="Fix the TypeError in api/handlers.py",
    context="""The file api/handlers.py has a TypeError on line 47:
    'NoneType' object has no attribute 'get'.
    The function process_request() receives a dict from parse_body(),
    but parse_body() returns None when Content-Type is missing.
    The project is at /home/user/myproject and uses Python 3.11."""
)

Subagent получает сфокусированный системный промпт, построенный на основе ваших goal и context, с инструкцией выполнить задачу и предоставить структурированную сводку: что было сделано, что найдено, какие файлы изменены и с какими проблемами столкнулись.

Практические примеры

Параллельное исследование

Исследуйте несколько тем одновременно и соберите сводки:

delegate_task(tasks=[
    {
        "goal": "Research the current state of WebAssembly in 2025",
        "context": "Focus on: browser support, non-browser runtimes, language support",
        "toolsets": ["web"]
    },
    {
        "goal": "Research the current state of RISC-V adoption in 2025",
        "context": "Focus on: server chips, embedded systems, software ecosystem",
        "toolsets": ["web"]
    },
    {
        "goal": "Research quantum computing progress in 2025",
        "context": "Focus on: error correction breakthroughs, practical applications, key players",
        "toolsets": ["web"]
    }
])

Ревью кода + исправление

Делегируйте workflow ревью и исправления в новый контекст:

delegate_task(
    goal="Review the authentication module for security issues and fix any found",
    context="""Project at /home/user/webapp.
    Auth module files: src/auth/login.py, src/auth/jwt.py, src/auth/middleware.py.
    The project uses Flask, PyJWT, and bcrypt.
    Focus on: SQL injection, JWT validation, password handling, session management.
    Fix any issues found and run the test suite (pytest tests/auth/).""",
    toolsets=["terminal", "file"]
)

Многофайловый рефакторинг

Делегируйте крупную задачу рефакторинга, которая перегрузила бы контекст родителя:

delegate_task(
    goal="Refactor all Python files in src/ to replace print() with proper logging",
    context="""Project at /home/user/myproject.
    Use the 'logging' module with logger = logging.getLogger(__name__).
    Replace print() calls with appropriate log levels:
    - print(f"Error: ...") -> logger.error(...)
    - print(f"Warning: ...") -> logger.warning(...)
    - print(f"Debug: ...") -> logger.debug(...)
    - Other prints -> logger.info(...)
    Don't change print() in test files or CLI output.
    Run pytest after to verify nothing broke.""",
    toolsets=["terminal", "file"]
)

Детали пакетного режима

Когда вы передаёте массив tasks, subagent запускаются параллельно с использованием пула потоков:

Одиночная задача делегируется напрямую, без накладных расходов пула потоков.

Переопределение модели

Вы можете настроить другую модель для subagent через config.yaml — полезно для делегирования простых задач более дешёвым/быстрым моделям:

# In ~/.hermes/config.yaml
delegation:
  model: "google/gemini-flash-2.0"    # Более дешёвая модель для subagent
  provider: "openrouter"              # Опционально: направить subagent другому провайдеру

Если не указано, subagent используют ту же модель, что и родитель.

Советы по выбору toolsets

Параметр toolsets определяет, к каким инструментам имеет доступ subagent. Выбирайте в зависимости от задачи:

Шаблон toolset Сценарий использования
["terminal", "file"] Работа с кодом, отладка, редактирование файлов, сборка
["web"] Исследования, проверка фактов, поиск документации
["terminal", "file", "web"] Полнофункциональные задачи (по умолчанию)
["file"] Анализ только для чтения, ревью кода без исполнения
["terminal"] Системное администрирование, управление процессами

Определённые toolsets заблокированы для subagent независимо от того, что вы укажете:

Максимум итераций

Каждый subagent имеет лимит итераций (по умолчанию: 50), который ограничивает количество его шагов с вызовом инструментов:

delegate_task(
    goal="Quick file check",
    context="Check if /etc/nginx/nginx.conf exists and print its first 10 lines",
    max_iterations=10  # Простая задача, не требует много шагов
)

Тайм-аут дочернего агента

Subagent считается зависшим и принудительно завершается, если он молчит дольше delegation.child_timeout_seconds секунд реального времени. Значение по умолчанию — 600 (10 минут) — увеличено с 300 секунд в более ранних версиях, потому что модели с высоким уровнем рассуждения при выполнении нетривиальных исследовательских задач завершались принудительно в процессе размышления. Настройте под свою установку:

delegation:
  child_timeout_seconds: 600   # по умолчанию

Уменьшайте для быстрых локальных моделей; увеличивайте для медленных моделей рассуждения над сложными задачами. Таймер сбрасывается каждый раз, когда дочерний агент совершает API-вызов или вызов инструмента — только действительно простаивающие рабочие процессы достигают тайм-аута.

Диагностический дамп при тайм-ауте без вызовов

Если subagent завершился по тайм-ауту, совершив ноль API-вызовов (обычно: провайдер недоступен, ошибка аутентификации или отклонение tool-schema), delegate_task записывает структурированную диагностику в ~/.hermes/logs/subagent-timeout-<session>-<timestamp>.log, содержащую снимок конфигурации subagent, трассировку разрешения учётных данных и любые ранние сообщения об ошибках. Это значительно упрощает поиск причин по сравнению с прежним бесшумным тайм-аутом.

Мониторинг запущенных subagent (/agents)

TUI предоставляет панель /agents (алиас /tasks), которая превращает рекурсивное разветвление delegate_task в полноценную поверхность аудита:

Классический CLI просто выводит /agents в виде текстовой сводки; TUI — это то, где панель раскрывается в полной мере. См. TUI — Slash commands.

Ограничение глубины и вложенная оркестрация

По умолчанию делегирование является плоским: родитель (глубина 0) порождает детей (глубина 1), и эти дети не могут делегировать дальше. Это предотвращает неконтролируемое рекурсивное делегирование.

Для многоэтапных workflows (исследование → синтез, или параллельная оркестрация подзадач), родитель может порождать детей-оркестраторов, которые могут делегировать задачи собственным рабочим процессам:

delegate_task(
    goal="Survey three code review approaches and recommend one",
    role="orchestrator",  # Позволяет этому потомку порождать собственных рабочих
    context="...",
)

Предупреждение о затратах: При max_spawn_depth: 3 и max_concurrent_children: 3 дерево может достичь 3×3×3 = 27 одновременных листовых агентов. Каждый дополнительный уровень умножает расходы — увеличивайте max_spawn_depth осознанно.

Время жизни и устойчивость

delegate_task синхронен — не является устойчивым

delegate_task выполняется внутри текущего шага родителя. Он блокирует родителя, пока каждый потомок не завершится (или не будет отменён). Это не фоновая очередь задач:

  • Если родитель прерван (пользователь отправил новое сообщение, /stop, /new), все активные потомки отменяются и возвращают status="interrupted". Их незавершённая работа отбрасывается.

  • Потомки не продолжают работу после завершения шага родителя.

  • Отменённые потомки возвращают структурированный результат (status="interrupted", exit_reason="interrupted"), но поскольку родитель также был прерван, этот результат часто не попадает в видимый пользователю ответ.

Для устойчивой долгосрочной работы, которая должна пережить прерывания или выйти за пределы текущего шага, используйте:

  • cronjob (action=create) — планирует отдельный запуск агента; устойчив к прерываниям родительского шага.

  • terminal(background=True, notify_on_complete=True) — долго выполняющиеся shell-команды, которые продолжают работу, пока агент занимается другими делами.

Ключевые свойства

Делегирование vs execute_code

Фактор delegate_task execute_code
Рассуждение Полный цикл рассуждения LLM Только выполнение Python-кода
Контекст Новый изолированный диалог Нет диалога, только скрипт
Доступ к инструментам Все незаблокированные инструменты с рассуждением 7 инструментов через RPC, без рассуждения
Параллельность 3 одновременных subagent по умолчанию (настраивается) Один скрипт
Лучше всего для Сложных задач, требующих суждения Механических многошаговых конвейеров
Стоимость токенов Выше (полный цикл LLM) Ниже (возвращается только stdout)
Взаимодействие с пользователем Нет (subagent не могут задавать уточняющие вопросы) Нет

Эмпирическое правило: Используйте delegate_task, когда подзадача требует рассуждения, суждения или многошагового решения проблем. Используйте execute_code, когда нужна механическая обработка данных или скриптовые workflows.

Конфигурация

# In ~/.hermes/config.yaml
delegation:
  max_iterations: 50                        # Максимум шагов на потомка (по умолчанию: 50)
  # max_concurrent_children: 3              # Параллельных потомков на пакет (по умолчанию: 3)
  # max_spawn_depth: 1                      # Глубина дерева (1-3, по умолчанию 1 = плоское). Увеличьте до 2, чтобы оркестраторы могли порождать листовых потомков; до 3 для трёх уровней.
  # orchestrator_enabled: true              # Отключите, чтобы принудительно установить всем потомкам роль leaf.
  model: "google/gemini-3-flash-preview"             # Опциональное переопределение модели/провайдера
  provider: "openrouter"                             # Опциональный встроенный провайдер

# Или используйте прямую пользовательскую конечную точку вместо провайдера:
delegation:
  model: "qwen2.5-coder"
  base_url: "http://localhost:1234/v1"
  api_key: "local-key"

Совет

Агент обрабатывает делегирование автоматически, исходя из сложности задачи. Вам не нужно явно просить его делегировать — он сделает это, когда это будет иметь смысл.