Внутреннее устройство Cron
Подсистема cron обеспечивает выполнение запланированных задач — от простых одноразовых задержек до повторяющихся задач с cron-выражениями, включая внедрение функций и кроссплатформенную доставку.
Ключевые файлы
| Файл | Назначение |
|---|---|
cron/jobs.py |
Модель задания, хранилище, атомарное чтение/запись в jobs.json |
cron/scheduler.py |
Цикл планировщика — Определение просроченных заданий, выполнение, отслеживание повторений |
инструменты/cronjob_tools.py |
Регистрация и обработка инструмента cronjob для моделей |
шлюз/run.py |
Интеграция с шлюзом — тактирование cron в долгоиграющем цикле |
hermes_cli/cron.py |
Подкоманды CLI hermes cron |
Модель планирования
Поддерживается четыре формы расписания:
| Формат | Пример | Поведение |
|---|---|---|
| Относительная задержка | 30м, 2ч, 1д |
Одноразовое, реализованное через указанный промежуток времени |
| Интервал | каждые 2 часа, каждые 30 м |
Повторяющееся, реализуется через равные промежутки времени |
| Cron-выражение | 0 9 * * * |
Стандартный синтаксис cron из 5 полей (минута, час, день, месяц, день недели) |
| ISO-метка времени | 2025-01-15T09:00:00 |
Одноразовое, реализовано в точное время |
Интерфейс для моделей представляет собой единый инструмент «cronjob» с операциями в стиле действий: «создать», «список», «обновить», «пауза», «возобновить», «запустить», «удалить».
Хранение заданий
Задание хранения в ~/.hermes/cron/jobs.json с семантикой атомарной записи (запись во временный файл, затем переименование). каждая запись задания содержит:
{
"id": "a1b2c3d4e5f6",
"name": "Ежедневный брифинг",
"prompt": "Обобщи сегодняшние новости об ИИ и раунды финансирования",
"schedule": {
"kind": "cron",
"expr": "0 9 * * *",
"display": "0 9 * * *"
},
"skills": ["ai-funding-daily-report"],
"deliver": "telegram:-1001234567890",
"repeat": {
"times": null,
"completed": 42
},
"state": "scheduled",
"enabled": true,
"next_run_at": "2025-01-16T09:00:00Z",
"last_run_at": "2025-01-15T09:00:00Z",
"last_status": "ok",
"created_at": "2025-01-01T00:00:00Z",
"model": null,
"provider": null,
"script": null
}
Состояния жизненного цикла задания
| Состояние | Значение |
|---|---|
запланировано |
Активно, срабатывает в следующее запланированное время |
приостановлено |
Приостановлено — не будет производиться до восстановления |
завершен |
Исчерпан лимит повторений или одноразовое задание уже сработало |
бег |
Выполняется в данный момент (переходное состояние) |
Обратная связь
В старых заданиях может быть одно поле «навыков» вместо массива «навыков». Планировщик нормализует это при включении — одиночный skill преобразуется в skills: [skill].
Среда выполнения планировщика
Цикл тактов
Планировщик работает периодически так (по умолчанию: женщины 60 секунд):
tick()
1. Захватить блокировку планировщика (предотвращает перекрытие тактов)
2. Загрузить все задания из jobs.json
3. Отфильтровать просроченные задания (next_run <= now И state == "scheduled")
4. Для каждого просроченного задания:
a. Установить состояние "running"
b. Создать новый сеанс AIAgent (без истории разговора)
c. Загрузить прикрепленные навыки по порядку (внедряются как сообщения пользователя)
d. Выполнить запрос задания через агента
e. Доставить ответ в настроенный целевой канал
f. Обновить run_count, вычислить next_run
g. Если лимит повторений исчерпан → state = "completed"
h. Иначе → state = "scheduled"
5. Записать обновленные задания обратно в jobs.json
6. Освободить блокировку планировщика
Интеграция с шлюзом
В режиме шлюза планировщик работает в выделенном фоновом потоке (_start_cron_ticker в gateway/run.py), который вызывает scheduler.tick() в течение 60 секунд параллельно с обработкой сообщений.
В режиме CLI задания cron срабатывают только при выполнении команды hermes cron или во время активных сеансов CLI.
Изоляция нового сеанса
Результаты каждого задания cron в совершенно новом сеансе агента:
- Нет истории разговоров из предыдущих запусков
- Нет памяти о выполнении предыдущих cron (если только не сохранено в памяти/файлах)
- Запрос должен быть самодостаточным — задание cron не может задавать уточняющие вопросы.
- Набор инструментов
cronjobотключен (защита от рекурсии)
Задания с навыками
Задание cron может прикрепить один или несколько навыков через поле skills. Во время выполнения:
- Навыки загружаются в указанном порядке.
- Содержимое SKILL.md, каждый навык внедряется как контекст.
- Запрос задания включается в качестве инструкции задачи.
- Агент обрабатывает объединенные контекстные функции + запрос.
Это позволяет повторно использовать рабочие процессы, протестированные рабочие процессы, без добавления полных инструкций в запрос cron. Например:
Создать ежедневный отчет о финансировании → прикрепить навык "ai-funding-daily-report"
Задания с поддержкой скриптов
Задания также могут прикрепить Python-скрипт через поле script. Скрипт до каждого шага агента, и его стандартный вывод включается в запрос как контекст. Это позволяет реализовать шаблоны сбора данных и обнаружения изменений:
# ~/.hermes/scripts/check_competitors.py
import requests, json
# Получить примечания к релизам конкурентов, сравнить с последним запуском
# Вывести сводку в stdout — агент анализирует и сообщает
Тайм-аут скрипта по умолчанию составляет 120 секунд. _get_script_timeout() устанавливает лимит через цепочку из трех уровней:
- Переопределение на уровне модуля —
_SCRIPT_TIMEOUT(для тестов/подмены). Используется только тогда, когда значения различаются по умолчанию. - Переменная окружения —
HERMES_CRON_SCRIPT_TIMEOUT - Конфигурация —
cron.script_timeout_секундывconfig.yaml(читается черезload_config()) - По умолчанию — 120 секунд.
Восстановление провайдера
run_job() передает настроенным пользователем резервные провайдеры и пул учетных данных в экземпляре AIAgent:
- Резервные провайдеры — читает
fallback_providers(список) илиfallback_model(устаревший словарь) изconfig.yaml, соответствуя шаблону_load_fallback_model()шлюза. Передается какfallback_model=вAIAgent.__init__, который нормализует оба в форме цепочки резервирования. - Пул учетных данных — загружается через
load_pool(provider)изagent.credential_pool, используя разрешенное имя провайдера времени выполнения. Передается только тогда, когда в пуле есть учетные данные (pool.has_credentials()). Обеспечивает ротацию ключей того же провайдера при ошибках 429/ограничения скорости.
Это повторяет поведение шлюза — без этого агенты cron не смогли бы восстановиться после отключения скорости.
Модель доставки
Результаты заданий cron могут быть выставлены на любую уязвимую платформу:
| Цель | Синтаксис | Пример |
|---|---|---|
| Исходный чат | происхождение |
Доставить в чат, где было создано задание |
| Локальный файл | местный |
Сохранить в ~/.hermes/cron/output/ |
| Телеграмма | telegram или telegram:<chat_id> |
телеграмма:-1001234567890 |
| Раздор | discord или discord:#channel |
discord:#engineering |
| слабый | слаба |
Добавить на домашний канал Slack |
WhatsApp |
Добавить в WhatsApp | |
| Сигнал | сигнал |
Доставить в Сигнал |
| Матрица | матрица |
Доставить во вторую комнату Матрица |
| Самое важное | самое важное |
Доставить в Mattermost |
| Электронная почта | электронная почта |
Доставить по электронной почте |
| СМС | смс |
Доставить по SMS |
| Домашний помощник | домашний помощник |
Доставить в разговор HA |
| ДинТок | дингтолк |
Доставить в DingTalk |
| Фейшу | фейшу |
Доставить в Фейшу |
| ВеКом | веком |
Доставить в WeCom |
| Вэйсинь | вэйсинь |
Добавить в Weixin (WeChat) |
| Синие пузыри | голубые пузыри |
Добавить в iMessage через BlueBubbles |
| QQ-бот | qqbot |
Доставить в QQ (Tencent) через Официальный API v2 |
Для темы Telegram воспользуйтесь форматом telegram:<chat_id>:<thread_id> (например, telegram:-1001234567890:17585).
Обработка ответа
По умолчанию (cron.wrap_response: true) cron доставки оборачиваются:
- Заголовком, идентифицирующим имя задания и должность.
- Нижним колонтитулом, отмечающим, что агент не может видеть представленное сообщение в разговоре.
Префикс [SILENT] в ответе cron полностью отключает доставку — полезен для заданий, которым нужно только записывать данные в файлы или выполнять сложные эффекты.
Сеанс изоляции
Доставки cron НЕ зеркалируются в истории разговора сеанса шлюза. Они существуют только в собственном сеансе задания cron. Это собственное нарушение чередования сообщений в разговоре целевого чата.
Защита от рекурсии
В сеансах запущенных cron набор инструментов cronjob отключается. Это собственное: - Создание новых задач cron запланированным заданием - Рекурсивное планирование, которое может привести к взрывному использованию токенов - Случайное изменение расписания заданий из самого задания
Блокировка
План использует межпроцессную блокировку файловой системы (fcntl.flock в Unix, msvcrt.locking в Windows) для предотвращения выполнения одного и того же пакета просроченных задач, которые перехватывают тактами — даже между внутрипроцессным тактовым генератором шлюза и вызовом Hermes cron / ручным tick(). Если блок поставки не может быть получен, tick() мгновенно возвращает 0.
Интерфейс командной строки
CLI hermes cron обеспечивает прямое управление заданиями:
hermes cron list # Показать все задания
hermes cron create # Интерактивное создание задания (псевдоним: add)
hermes cron edit <job_id> # Редактировать конфигурацию задания
hermes cron pause <job_id> # Приостановить выполняющееся задание
hermes cron resume <job_id> # Возобновить приостановленное задание
hermes cron run <job_id> # Запустить немедленное выполнение
hermes cron remove <job_id> # Удалить задание