Внутреннее устройство Cron
Подсистема cron обеспечивает выполнение запланированных задач — от простых одноразовых задержек до повторяющихся заданий по cron-выражениям с подключением навыков и доставкой на разные платформы.
Ключевые файлы
| Файл | Назначение |
|---|---|
cron/jobs.py |
Модель задания, хранилище, атомарная запись/чтение jobs.json |
cron/scheduler.py |
Цикл планировщика — обнаружение просроченных заданий, выполнение, отслеживание повторов |
tools/cronjob_tools.py |
Регистрация и обработчик инструмента cronjob для модели |
gateway/run.py |
Интеграция с gateway — тики cron в долгоживущем цикле |
hermes_cli/cron.py |
Подкоманды CLI hermes cron |
Модель планирования
Поддерживаются четыре формата расписания:
| Формат | Пример | Поведение |
|---|---|---|
| Относительная задержка | 30m, 2h, 1d |
Одноразово, срабатывает через указанный промежуток времени |
| Интервал | every 2h, every 30m |
Повторяющееся, срабатывает через равные промежутки времени |
| Cron-выражение | 0 9 * * * |
Стандартный 5-польный синтаксис cron (минута, час, день, месяц, день недели) |
| ISO-метка времени | 2025-01-15T09:00:00 |
Одноразово, срабатывает в точное время |
Пользовательская поверхность для модели — это единый инструмент cronjob с операциями в стиле действий: create, list, update, pause, resume, run, remove.
Хранение заданий
Задания хранятся в ~/.hermes/cron/jobs.json с атомарной записью (запись во временный файл, затем переименование). Каждая запись задания содержит:
{
"id": "a1b2c3d4e5f6",
"name": "Daily briefing",
"prompt": "Summarize today's AI news and funding rounds",
"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
}
Состояния жизненного цикла задания
| Состояние | Значение |
|---|---|
scheduled |
Активно, сработает в следующее запланированное время |
paused |
Приостановлено — не сработает до возобновления |
completed |
Лимит повторов исчерпан или одноразовое задание выполнено |
running |
В данный момент выполняется (переходное состояние) |
Обратная совместимость
У старых заданий может быть одно поле skill вместо массива skills. Планировщик нормализует это при загрузке — одиночное skill повышается до skills: [skill].
Среда выполнения планировщика
Цикл тиков
Планировщик работает на периодических тиках (по умолчанию: каждые 60 секунд):
tick()
1. Acquire scheduler lock (prevents overlapping ticks)
2. Load all jobs from jobs.json
3. Filter to due jobs (next_run <= now AND state == "scheduled")
4. For each due job:
a. Set state to "running"
b. Create fresh AIAgent session (no conversation history)
c. Load attached skills in order (injected as user messages)
d. Run the job prompt through the agent
e. Deliver the response to the configured target
f. Update run_count, compute next_run
g. If repeat count exhausted → state = "completed"
h. Otherwise → state = "scheduled"
5. Write updated jobs back to jobs.json
6. Release scheduler lock
Интеграция с gateway
В режиме gateway планировщик работает в выделенном фоновом потоке (_start_cron_ticker в gateway/run.py), который вызывает scheduler.tick() каждые 60 секунд параллельно с обработкой сообщений.
В режиме CLI cron-задания срабатывают только при выполнении команд hermes cron или во время активных CLI-сессий.
Изоляция свежих сессий
Каждое cron-задание выполняется в полностью новой сессии агента:
-
Нет истории разговора из предыдущих запусков
-
Нет памяти о предыдущих выполнениях cron (если она не сохранена в память/файлы)
-
Промпт должен быть самодостаточным — cron-задания не могут задавать уточняющих вопросов
-
Набор инструментов
cronjobотключён (защита от рекурсии)
Задания с навыками
Cron-задание может подключать один или несколько навыков через поле skills. Во время выполнения:
-
Навыки загружаются в указанном порядке
-
Содержимое SKILL.md каждого навыка вставляется как контекст
-
Промпт задания добавляется как инструкция задачи
-
Агент обрабатывает объединённый контекст навыков и промпт
Это позволяет использовать переиспользуемые, протестированные рабочие процессы без вставки полных инструкций в cron-промпты. Например:
Create a daily funding report → attach "ai-funding-daily-report" skill
Задания со скриптами
Задания также могут подключать Python-скрипт через поле script. Скрипт выполняется перед каждым шагом агента, и его stdout вставляется в промпт как контекст. Это позволяет реализовать сбор данных и обнаружение изменений:
# ~/.hermes/scripts/check_competitors.py
import requests, json
# Fetch competitor release notes, diff against last run
# Print summary to stdout — agent analyzes and reports
Тайм-аут скрипта по умолчанию составляет 120 секунд. _get_script_timeout() вычисляет лимит через трёхуровневую цепочку:
-
Переопределение на уровне модуля —
_SCRIPT_TIMEOUT(для тестов/монки-патчинга). Используется только когда отличается от значения по умолчанию. -
Переменная окружения —
HERMES_CRON_SCRIPT_TIMEOUT -
Конфиг —
cron.script_timeout_secondsвconfig.yaml(читается черезload_config()) -
Значение по умолчанию — 120 секунд
Восстановление провайдера
run_job() передаёт настроенные fallback-провайдеры и пул учётных данных пользователя в экземпляр AIAgent:
-
Fallback-провайдеры — читает
fallback_providers(список) илиfallback_model(устаревший словарь) изconfig.yaml, повторяя паттерн_load_fallback_model()из gateway. Передаётся какfallback_model=вAIAgent.__init__, который нормализует оба формата в цепочку fallback. -
Пул учётных данных — загружается через
load_pool(provider)изagent.credential_poolс использованием разрешённого имени провайдера времени выполнения. Передаётся только когда в пуле есть учётные данные (pool.has_credentials()). Обеспечивает ротацию ключей одного провайдера при ошибках 429/rate-limit.
Это повторяет поведение gateway — без этого cron-агенты не могли бы восстанавливаться после превышения лимитов запросов.
Модель доставки
Результаты cron-заданий могут доставляться на любую поддерживаемую платформу:
| Цель | Синтаксис | Пример |
|---|---|---|
| Исходный чат | origin |
Доставка в чат, где было создано задание |
| Локальный файл | local |
Сохранение в ~/.hermes/cron/output/ |
| Telegram | telegram или telegram:<chat_id> |
telegram:-1001234567890 |
| Discord | discord или discord:#channel |
discord:#engineering |
| Slack | slack |
Доставка в домашний канал Slack |
whatsapp |
Доставка в WhatsApp | |
| Signal | signal |
Доставка в Signal |
| Matrix | matrix |
Доставка в домашнюю комнату Matrix |
| Mattermost | mattermost |
Доставка в Mattermost |
email |
Доставка по email | |
| SMS | sms |
Доставка по SMS |
| Home Assistant | homeassistant |
Доставка в HA conversation |
| DingTalk | dingtalk |
Доставка в DingTalk |
| Feishu | feishu |
Доставка в Feishu |
| WeCom | wecom |
Доставка в WeCom |
| Weixin | weixin |
Доставка в Weixin (WeChat) |
| BlueBubbles | bluebubbles |
Доставка в iMessage через BlueBubbles |
| QQ Bot | qqbot |
Доставка в QQ (Tencent) через Official API v2 |
Для топиков Telegram используйте формат telegram:<chat_id>:<thread_id> (например, telegram:-1001234567890:17585).
Обёртка ответов
По умолчанию (cron.wrap_response: true) доставка cron оборачивается:
-
Заголовком с именем cron-задания и задачей
-
Подвалом с уведомлением, что агент не видит доставленное сообщение в разговоре
Префикс [SILENT] в ответе cron полностью отключает доставку — полезно для заданий, которым нужно только записывать файлы или выполнять побочные эффекты.
Изоляция сессии
Доставка cron НЕ отражается в истории разговора сессии gateway. Она существует только в собственной сессии cron-задания. Это предотвращает нарушение чередования сообщений в целевом чате.
Защита от рекурсии
В сессиях, запущенных cron, набор инструментов cronjob отключён. Это предотвращает:
-
Создание новых cron-заданий из запланированного задания
-
Рекурсивное планирование, которое могло бы взорвать использование токенов
-
Случайное изменение расписания задания изнутри самого задания
Блокировка
Планировщик использует межпроцессную файловую блокировку (fcntl.flock на Unix, msvcrt.locking на Windows) для предотвращения выполнения одной и той же партии просроченных заданий дважды при перекрывающихся тиках — даже между внутрипроцессным тикером gateway и отдельным вызовом hermes cron или ручным tick(). Если блокировку не удаётся получить, tick() немедленно возвращает 0.
CLI-интерфейс
CLI hermes cron предоставляет прямое управление заданиями:
hermes cron list # Show all jobs
hermes cron create # Interactive job creation (alias: add)
hermes cron edit <job_id> # Edit job configuration
hermes cron pause <job_id> # Pause a running job
hermes cron resume <job_id> # Resume a paused job
hermes cron run <job_id> # Trigger immediate execution
hermes cron remove <job_id> # Delete a job