В pi нет шедулера. README перечисляет таймеры, cron, отложенные задачи и фоновый bash в списке намеренных пропусков рядом с sub-agents, plan mode, MCP и permission popups. Это намеренное решение — внутри pi их и не появится. Под @mariozechner/* шедулинг-пакетов тоже нет, всё что есть написали посторонние поверх runtime-API.

API при этом простой до неприличия. pi.sendUserMessage(text, { deliverAs: "steer" | "followUp" | "nextTurn" }) шлёт сообщение от имени пользователя. Все расширения ниже сводятся к этому вызову. Таймер сработал, расширение дёрнуло sendUserMessage, агент видит промпт и исполняет. pi.appendEntry(...) пишет произвольный JSON на диск, в контекст модели не идёт, нужен только чтобы пережить рестарт. На session_start расширение поднимает свои таймеры из файла, на session_shutdown гасит. Остальное — обвязка вокруг croner или голого setTimeout.


Самое полное из расширений — tintinweb/pi-schedule-prompt. Расширение регистрирует tool schedule_prompt и команду /schedule-prompt. Поддерживает cron, интервал вроде 5m или 1h, один выстрел по ISO или относительному +10m.

Расширение атомарно пишет состояние в .pi/schedule-prompts.json в корне проекта. Файл к session-id не привязан, поэтому переживает рестарт pi. На старте расширение читает файл, восстанавливает объекты задач, заново ставит таймеры. Когда таймер срабатывает, расширение дёргает pi.sendUserMessage. Одноразовая задача с прошедшим временем на загрузке сама себя выключает — архитектурно правильно, на практике иногда сюрприз, когда pi простоял ночь и утром половина напоминаний оказалась дезактивирована.

Расширение не отрабатывает срабатывания, которые пришлись на выключенный pi. croner с дефолтами идёт только вперёд, опцию catch ни одно из расширений не включает. Ноут стоял закрытый с трёх ночи до девяти утра — ежечасная задача не выстрелит шесть раз с утра, отработает в ближайшие десять и продолжит как ни в чём не бывало.


emanuelcasco/pi-mono-extensions/loop — порт /loop из Claude Code, и он проще на порядок. Только интервал, ни cron, ни одноразовых. Суффиксы s/m/h/d, дефолт десять минут, минимум десять секунд, через семь суток таймер сам гаснет.

Состояния на диске нет — всё в памяти процесса. На session_shutdown расширение чистит таймеры, рестарт pi обнуляет расписание подчистую. Если агент в момент тика занят, расширение шлёт сообщение как followUp, чтобы не рвать текущий ход.

Это инструмент для опросов внутри открытой сессии. Закрыл pi — расписание умерло. Восстановления авторы не предусмотрели и так задумывали.


@tintinweb/pi-subagents формально про другое. Основная фича — изолированные sub-agents в стиле Claude Code, а шедулинг автор добавил опциональным полем schedule у tool’а Agent. Тройка типов та же, что в pi-schedule-prompt.

Срабатывание спавнит фонового sub-agent в свежей дочерней сессии. Его результат уходит в родительскую через followUp. Состояние лежит в <cwd>/.pi/subagent-schedules/<sessionId>.json с блокировкой по PID, чтобы два инстанса не подрались. Ключ — session-id, отсюда жёсткая привязка. /new теряет расписание текущей сессии, /resume <id> поднимает его обратно. Безголовый pi -p запланированных sub-agents не ждёт — для CI это важно знать.

Поле schedule несовместимо с inherit_context (родительского контекста на момент срабатывания нет) и с resume (расписание плодит свежих, не воскрешает старых). Через /agents → Settings → Scheduling → disabled поле можно выкинуть из спецификации tool’а и сэкономить несколько сот токенов в контексте.


Все три расширения упираются в одно общее ограничение — шедулер живёт в Node-процессе pi и существует ровно столько, сколько крутится процесс. Отсюда и потерянные срабатывания, и сброс расписания на /new у sub-agents, и эфемерность pi-mono-loop, и несовместимость с безголовым pi -p.

Отдельная проблема — безопасность. Pi-пакеты работают с полным доступом к системе, расширения это произвольный код, скиллы могут велеть модели запускать произвольные бинари. README pi-mono предупреждает явно. Ставишь чужой шедулинг-пакет — ставишь чужого демона с правами своего юзера, без песочницы по умолчанию.


Качественно другая архитектура — pi-mom. Это самостоятельный пакет в той же монорепе badlogic/pi-mono, отдельный от coding-agent’а. Slack-бот поверх собственной обвязки, со своим runtime’ом и движком событий.

Три типа знакомые — immediate, one-shot с полем at, periodic с cron-выражением и явным IANA-полем timezone. Принципиальное отличие в том, как события приходят. Каждое событие — это JSON-файл в data/events/, и обвязка слушает каталог через fs.watch() со 100-миллисекундным дебаунсом.

Любой внешний процесс — системный cron, GitHub-вебхук, CI-задача — может разбудить mom без Slack и без RPC. Достаточно атомарно записать JSON в каталог. Просроченные immediate-события обвязка отсеивает по mtime. Если файл создан раньше harness.startTime, обвязка удаляет его без выполнения. С periodic всё та же история, что у расширений в coding-agent — пропущенные срабатывания теряются.

Для шумных опросов есть приём [SILENT]. Задача отвечает этим токеном, mom удаляет статусное сообщение и в канал не постит ничего. Архитектурно это шина сообщений в unix-стиле поверх ФС. Mom полностью снимает требование “pi должен крутиться непрерывно”.


В качестве референса промышленного шедулинга поверх pi-mono SDK — OpenClaw cron. Определения задач лежат в ~/.openclaw/cron/jobs.json, runtime-состояние в соседнем *-state.json. В сам pi не входит, продукт-обёртка, но архитектурно показателен.

OpenClaw поддерживает четыре режима таргета сессии. main шлёт системное событие в основную чат-сессию плюс опциональный пробуждающий heartbeat. isolated поднимает свежую сессию cron:<jobId> без переноса контекста — для ежедневных отчётов и фоновых рутин. current биндит задачу к сессии в момент создания. session:<custom-id> — именованная переживающая сессия для воркфлоу, накапливающих историю.

Одноразовые задачи получают до трёх ретраев с экспоненциальной задержкой на временные ошибки (rate-limit, overload, сеть, 5xx). Постоянные ошибки сразу выключают задачу. Повторяющиеся идут с задержкой 30s → 1m → 5m → 15m → 60m, которая сбрасывается после первого успеха.

Cron-выражения на ровный час OpenClaw сам размазывает по случайному окну до пяти минут, иначе на 0 * * * * все задачи стартуют залпом. Можно форсировать --exact или задать --stagger 30s.

Перед каждым запуском в режиме isolated cron пингует endpoint локального провайдера (ollama, openai-completions на loopback или .local) и при недоступности маркирует прогон как пропущенный без обращения к модели. Пинг кэшируется на пять минут, чтобы пакет созревших задач не устроил лавину запросов в мёртвый локальный vLLM.

cron.sessionRetention (дефолт 24ч) выбивает старые изолированные сессии. Журналы прогонов cron/runs/<jobId>.jsonl OpenClaw авто-усекает по runLog.maxBytes и keepLines. Льготное окно на задачу — пять минут, после которых обслуживающий цикл может пометить её как потерянную.

Полезный чеклист того, чего расширения coding-agent’а не делают. Ни разноса, ни повторов, ни префлайта, ни ротации журналов, ни обслуживания. Именно это отделяет удобную тулзу для агента от шедулера, на который можно положиться.


Если нужна реальная гарантия после ребута — выходишь за pi. Пользовательский таймер systemd с Persistent=true дёргает pi -p "<prompt>" сразу при загрузке, если время срабатывания пришлось на простой машины. В in-process решениях такого нет. Цена — каждый запуск это одноразовая сессия без контекста, годится для атомарных задач без долгой памяти.

pi-mom работает мостом через ФС, если в качестве канала устраивает Slack. Внешний cron или вебхук пишет JSON в data/events/, mom его подхватывает.

Свой долгоживущий супервизор поверх pi --mode rpc — путь для встраиваемых IDE-сценариев и кастомных оркестраторов. Демон держит JSONL-протокол на stdin/stdout pi и шедулит по своему расписанию. В этом подходе нельзя пользоваться readline Node — он бьёт ещё и по U+2028 и U+2029, валидным внутри JSON-строк. Нужен строгий разделитель \n.

Готовая обёртка — OpenClaw. Если многоканальная модель устраивает, писать своё не нужно.


Pi сознательно отдал шедулинг в userspace. In-process расширения работают только пока открыта сессия, и любая задача, которая обязана сработать после ребута, решается вне pi — через pi-mom, через systemd, через свой супервизор поверх RPC или через готовый OpenClaw.

Не путать mitsupi/loop.ts (Armin Ronacher) с emanuelcasco/.../loop. mitsupi/loop.ts — цикл повторной подачи, замыкающий агента самого на себя для автономного режима. К шедулингу mitsupi отношения не имеет, категории разные.