Перейти к основному содержимому

Внутреннее устройство Cron

Подсистема cron обеспечивает выполнение запланированных задач — от простых одноразовых задержек до повторяющихся заданий с cron-выражениями, включая внедрение навыков и кроссплатформенную доставку.

Ключевые файлы​

ФайлНазначение
cron/jobs.pyМодель задания, хранилище, атомарное чтение/запись в jobs.json
cron/scheduler.pyЦикл планировщика — обнаружение просроченных заданий, выполнение, отслеживание повторов
tools/cronjob_tools.pyРегистрация и обработчик инструмента cronjob для модели
gateway/run.pyИнтеграция шлюза — тиканье cron в долгоиграющем цикле
vibeos_cli/cron.pyПодкоманды CLI vibeos 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.

Хранение заданий​

Задания хранятся в ~/.vibeos/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
}

Состояния жизненного цикла задания​

СостояниеЗначение
scheduledАктивно, сработает в следующее запланированное время
pausedПриостановлено — не сработает до возобновления
completedЛимит повторов исчерпан или одноразовое задание сработало
runningВ настоящее время выполняется (переходное состояние)

Обратная совместимость​

У старых заданий может быть одно поле skill вместо массива skills. Планировщик нормализует это при загрузке — одиночный 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. Освободить блокировку планировщика

Интеграция шлюза​

В режиме шлюза триггер cron (часть, которая решает, когда срабатывает просроченное задание — «Ось B») выбирается через подключаемого провайдера CronScheduler. Шлюз вызывает resolve_cron_scheduler() (cron/scheduler_provider.py) и запускает start() разрешенного провайдера в выделенном фоновом потоке, наряду с отдельным потоком обслуживания шлюза.

Активный провайдер выбирается ключом конфигурации cron.provider:

  • пусто (по умолчанию) → встроенный InProcessCronScheduler, который запускает исторический внутрипроцессный цикл, вызывающий scheduler.tick() каждые 60 секунд. Это побайтово идентично поведению до провайдера.
  • именованный провайдер (например, chronos, управляемый cron-провайдер для развертываний с масштабированием до нуля) → обнаруживается из plugins/cron/&lt;name&gt;/ или $VIBEOS_HOME/plugins/&lt;name&gt;/.

Если именованный провайдер отсутствует, не загружается или сообщает is_available() == False, резолвер возвращается к встроенному с предупреждением — cron никогда не остается без триггера. Встроенный провайдер находится в ядре (cron/scheduler_provider.py), а не в plugins/, поэтому его нельзя случайно удалить.

Что означает «срабатывание» (выполнение задания + доставка) — не меняется и является общим для всех провайдеров; оно остается в scheduler.run_job() / scheduler._deliver_result(). Провайдер управляет только триггером, никогда — выполнением.

В режиме CLI задания cron срабатывают только при выполнении команд vibeos cron или во время активных сеансов CLI.

Управляемый cron (Chronos) для масштабирования до нуля​

Хостируемые шлюзы могут запускать провайдер Chronos (cron.provider: chronos) вместо встроенного тикера. Chronos позволяет простаивающему шлюзу масштабироваться до нуля и при этом выполнять задания cron: вместо 60-секундного внутрипроцессного цикла (который держал бы процесс активным) он просит инфраструктуру Nous вооружить ровно один управляемый одноразовый вызов на каждое задание в его реальное время следующего срабатывания. В момент срабатывания Nous вызывает шлюз обратно через аутентифицированный вебхук (POST /api/cron/fire); шлюз выполняет задание через тот же путь run_one_job, что и встроенный, затем перевооружает следующий одноразовый вызов. Между срабатываниями процесс может быть полностью остановлен — он просыпается только при реальном срабатывании, никогда по периодическому таймеру.

Поток (управляемый планировщик предоставляется Nous; агент не хранит учетные данные планировщика):

создание/обновление задания cron
→ Chronos просит Nous вооружить одноразовый вызов в next_run_at задания
(аутентифицируется существующим токеном Nous агента)
→ в момент срабатывания Nous вызывает шлюз: POST {callback_url}/api/cron/fire
(аутентифицируется краткосрочным, целевым JWT, выпущенным Nous)
→ шлюз проверяет токен, захватывает задание (хранилище compare-and-set, чтобы
развертывания с несколькими репликами срабатывали не более одного раза), выполняет его и перевооружает следующий одноразовый вызов

Конфигурация (все несекретно; на хостируемых агентах Nous устанавливает это при предоставлении):

ключзначение
cron.providerchronos для активации (пусто = встроенный тикер)
cron.chronos.portal_urlБазовый URL Nous (вооружение + эмитент токена срабатывания)
cron.chronos.callback_urlСобственный публичный базовый URL шлюза для входящих срабатываний
cron.chronos.expected_audienceАудитория токена срабатывания этого агента
cron.chronos.nas_jwks_urlНабор ключей для проверки входящего токена срабатывания

Если Chronos настроен неправильно или агент не вошел в Nous, resolve_cron_scheduler() возвращается к встроенному тикеру (с предупреждением в журнале) — cron никогда не теряет свой триггер. Повторяющиеся задания перевооружаются после каждого срабатывания; задания с repeat-N чисто останавливаются, когда счетчик исчерпан (без осиротевших одноразовых вызовов). Полный контракт взаимодействия агент↔Nous описан в docs/chronos-managed-cron-contract.md.

Изоляция свежего сеанса​

Каждое задание cron выполняется в полностью новом сеансе агента:

  • Нет истории разговора из предыдущих запусков
  • Нет памяти о предыдущих выполнениях cron (если не сохранено в память/файлы)
  • Запрос должен быть самодостаточным — задания cron не могут задавать уточняющие вопросы
  • Набор инструментов cronjob отключен (защита от рекурсии)

Задания с поддержкой навыков​

Задание cron может прикрепить один или несколько навыков через поле skills. Во время выполнения:

  1. Навыки загружаются в указанном порядке
  2. Содержимое SKILL.md каждого навыка внедряется как контекст
  3. Запрос задания добавляется как инструкция задачи
  4. Агент обрабатывает объединенный контекст навыка + запрос

Это позволяет использовать повторно используемые, протестированные рабочие процессы без вставки полных инструкций в запросы cron. Например:

Создать ежедневный отчет о финансировании → прикрепить навык "ai-funding-daily-report"

Задания с поддержкой скриптов​

Задания также могут прикреплять Python-скрипт через поле script. Скрипт выполняется до каждого хода агента, и его stdout внедряется в запрос как контекст. Это позволяет реализовать шаблоны сбора данных и обнаружения изменений:

# ~/.vibeos/scripts/check_competitors.py
import requests, json
# Получить примечания к релизам конкурентов, сравнить с последним запуском
# Вывести сводку в stdout — агент анализирует и сообщает

Тайм-аут скрипта по умолчанию составляет 120 секунд. _get_script_timeout() разрешает лимит через трехслойную цепочку:

  1. Переопределение на уровне модуля — _SCRIPT_TIMEOUT (для тестов/подмены). Используется только когда отличается от значения по умолчанию.
  2. Переменная окружения — VIBEOS_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Сохранить в ~/.vibeos/cron/output/
Telegramtelegram или `telegram:<chat_id>telegram:-1001234567890
Discorddiscord или discord:#channeldiscord:#engineering
SlackslackДоставить в домашний канал Slack
WhatsAppwhatsappДоставить в WhatsApp
SignalsignalДоставить в Signal
MatrixmatrixДоставить в домашнюю комнату Matrix
MattermostmattermostДоставить в Mattermost
EmailemailДоставить по email
SMSsmsДоставить по SMS
Home AssistanthomeassistantДоставить в разговор HA
DingTalkdingtalkДоставить в DingTalk
FeishufeishuДоставить в Feishu
WeComwecomДоставить в WeCom
WeixinweixinДоставить в Weixin (WeChat)
BlueBubblesbluebubblesДоставить в iMessage через BlueBubbles
QQ BotqqbotДоставить в QQ (Tencent) через Official API v2

Для тем Telegram используйте формат telegram:&lt;chat_id&gt;:&lt;thread_id&gt; (например, telegram:-1001234567890:17585`).

Оборачивание ответа​

По умолчанию (cron.wrap_response: true) доставки cron оборачиваются:

  • Заголовком, идентифицирующим имя задания cron и задачу
  • Нижним колонтитулом, отмечающим, что агент не может видеть доставленное сообщение в разговоре

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

Изоляция сеанса​

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

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

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

  • Создание новых заданий cron запланированным заданием
  • Рекурсивное планирование, которое могло бы взорвать использование токенов
  • Случайную мутацию расписания задания изнутри самого задания

Блокировка​

Планировщик использует межпроцессную файловую блокировку (fcntl.flock на Unix, msvcrt.locking на Windows) для предотвращения выполнения одного и того же пакета просроченных заданий дважды перекрывающимися тиками — даже между внутрипроцессным тикером шлюза и отдельным вызовом vibeos cron / ручным tick(). Если блокировку не удается захватить, tick() немедленно возвращает 0.

Интерфейс CLI​

CLI vibeos cron предоставляет прямое управление заданиями:

vibeos cron list                    # Показать все задания
vibeos cron create # Интерактивное создание задания (псевдоним: add)
vibeos cron edit <job_id> # Редактировать конфигурацию задания
vibeos cron pause <job_id> # Приостановить выполняющееся задание
vibeos cron resume <job_id> # Возобновить приостановленное задание
vibeos cron run <job_id> # Запустить немедленное выполнение
vibeos cron remove <job_id> # Удалить задание

Связанная документация​