Internals
На этой странице Подсистема cron обеспечивает выполнение задач по расписанию — от простых одноразовых задержек до повторяющихся заданий с cron-выражениями, с возможностью подключения навыков и кроссплатформенной доставкой.
Ключевые файлы¶
| Файл | Назначение |
|---|---|
cron/jobs.py |
Модель задания, хранилище, атомарное чтение/запись jobs.json |
cron/scheduler.py |
Цикл планировщика — обнаружение просроченных заданий, выполнение, отслеживание повторений |
tools/cronjob_tools.py |
Регистрация и обработчик инструмента cronjob для модели |
gateway/run.py |
Интеграция со шлюзом — тик 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 с атомарной записью (запись во временный файл, затем переименование). Каждая запись задания содержит:
[code]
{
"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
}
[/code]
Состояния жизненного цикла задания¶
| Состояние | Значение |
|---|---|
scheduled |
Активно, сработает в следующее запланированное время |
paused |
Приостановлено — не сработает до возобновления |
completed |
Лимит повторений исчерпан или одноразовое задание выполнено |
running |
Выполняется в данный момент (переходное состояние) |
Обратная совместимость¶
В старых заданиях может быть одно поле skill вместо массива skills. Планировщик нормализует это при загрузке — одиночный skill повышается до skills: [skill].
Среда выполнения планировщика¶
Цикл тиков¶
Планировщик работает по периодическому тику (по умолчанию: каждые 60 секунд):
[code]
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
[/code]
Интеграция со шлюзом¶
В режиме шлюза планировщик работает в выделенном фоновом потоке (_start_cron_ticker в gateway/run.py), который вызывает scheduler.tick() каждые 60 секунд параллельно с обработкой сообщений.
В режиме CLI задания cron срабатывают только при выполнении команд hermes cron или во время активных сессий CLI.
Изоляция свежих сессий¶
Каждое задание cron выполняется в полностью новой сессии агента:
* Нет истории диалога с предыдущих запусков
* Нет памяти о предыдущих выполнениях cron (если не сохранены в память/файлы)
* Запрос должен быть самодостаточным — задания cron не могут задавать уточняющие вопросы
* Набор инструментов cronjob отключён (защита от рекурсии)
Задания с навыками¶
Задание cron может подключать один или несколько навыков через поле skills. Во время выполнения:
1. Навыки загружаются в указанном порядке
2. Содержимое SKILL.md каждого навыка внедряется как контекст
3. Запрос задания добавляется как инструкция задачи
4. Агент обрабатывает объединённый контекст навыков + запрос
Это позволяет использовать переиспользуемые, протестированные рабочие процессы без вставки полных инструкций в запросы cron. Например:
[code] Create a daily funding report → attach "ai-funding-daily-report" skill
[/code]
Задания со скриптами¶
Задания также могут подключать Python-скрипт через поле script. Скрипт выполняется перед каждым шагом агента, и его стандартный вывод внедряется в запрос как контекст. Это позволяет реализовать сбор данных и обнаружение изменений:
[code]
# ~/.hermes/scripts/check_competitors.py
import requests, json
# Fetch competitor release notes, diff against last run
# Print summary to stdout — agent analyzes and reports
[/code]
Тайм-аут скрипта по умолчанию — 120 секунд. _get_script_timeout() определяет лимит через цепочку из трёх уровней:
1. Переопределение на уровне модуля — _SCRIPT_TIMEOUT (для тестов/monkeypatching). Используется только когда отличается от значения по умолчанию.
2. Переменная окружения — HERMES_CRON_SCRIPT_TIMEOUT
3. Конфиг — cron.script_timeout_seconds в config.yaml (читается через load_config())
4. По умолчанию — 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 могут быть доставлены на любую поддерживаемую платформу:
| Цель | Синтаксис | Пример |
|---|---|---|
| Исходный чат | 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 |
| 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 НЕ зеркалируются в историю диалога сессии шлюза. Они существуют только в собственной сессии задания cron. Это предотвращает нарушения чередования сообщений в диалоге целевого чата.
Защита от рекурсии¶
В сессиях, выполняемых cron, набор инструментов cronjob отключён. Это предотвращает:
* Создание новых заданий cron из запланированного задания
* Рекурсивное планирование, которое могло бы взорвать использование токенов
* Случайное изменение расписания задания изнутри самого задания
Блокировка¶
Планировщик использует межпроцессную файловую блокировку (fcntl.flock на Unix, msvcrt.locking на Windows), чтобы предотвратить выполнение одной и той же партии просроченных заданий дважды из-за перекрывающихся тиков — даже между внутрипроцессным тикером шлюза и отдельным вызовом hermes cron / ручным tick(). Если блокировку не удаётся получить, tick() сразу возвращает 0.
Интерфейс CLI¶
CLI hermes cron предоставляет прямое управление заданиями:
[code]
hermes cron list # Показать все задания
hermes cron create # Интерактивное создание задания (псевдоним: add)
hermes cron edit
hermes cron pause
hermes cron resume
hermes cron run
hermes cron remove
[/code]