Внутреннее устройство 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-задание может подключать один или несколько навыков через поле skills. Во время выполнения:

  1. Навыки загружаются в указанном порядке

  2. Содержимое SKILL.md каждого навыка вставляется как контекст

  3. Промпт задания добавляется как инструкция задачи

  4. Агент обрабатывает объединённый контекст навыков и промпт

Это позволяет использовать переиспользуемые, протестированные рабочие процессы без вставки полных инструкций в 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() вычисляет лимит через трёхуровневую цепочку:

  1. Переопределение на уровне модуля_SCRIPT_TIMEOUT (для тестов/монки-патчинга). Используется только когда отличается от значения по умолчанию.

  2. Переменная окруженияHERMES_CRON_SCRIPT_TIMEOUT

  3. Конфигcron.script_timeout_seconds в config.yaml (читается через load_config())

  4. Значение по умолчанию — 120 секунд

Восстановление провайдера

run_job() передаёт настроенные fallback-провайдеры и пул учётных данных пользователя в экземпляр AIAgent:

Это повторяет поведение 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 Доставка в WhatsApp
Signal signal Доставка в Signal
Matrix matrix Доставка в домашнюю комнату Matrix
Mattermost mattermost Доставка в Mattermost
Email 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 оборачивается:

Префикс [SILENT] в ответе cron полностью отключает доставку — полезно для заданий, которым нужно только записывать файлы или выполнять побочные эффекты.

Изоляция сессии

Доставка cron НЕ отражается в истории разговора сессии gateway. Она существует только в собственной сессии cron-задания. Это предотвращает нарушение чередования сообщений в целевом чате.

Защита от рекурсии

В сессиях, запущенных cron, набор инструментов cronjob отключён. Это предотвращает:

Блокировка

Планировщик использует межпроцессную файловую блокировку (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