Background task queue for AI CLIs — schedule prompts, retry on rate limits, manage everything via Web UI or Telegram bot.
Works with Claude Code, OpenAI Codex, Qwen Code, Cursor Agent, or any CLI that accepts a prompt argument.
Универсальный планировщик промптов для AI CLI — очередь, планирование и автоматический retry.
Работает с любым AI CLI: Claude Code, Codex, Qwen Code и другими.
Одна очередь для разовых задач, расписаний и автономных workflow.
- Мульти-провайдер — Claude, Codex, Qwen, Cursor Agent, или любой свой CLI
- Очередь задач с приоритетами (1 — высший, 10 — низший)
- Планирование — запуск промптов в заданное время
- Выбор модели — для Claude Code провайдеров (sonnet/opus/haiku) и любых провайдеров с явным списком
models(Web UI + бот) - Rate limit detection — автоматическое определение лимитов API; перегрузка провайдера (529 overloaded) отличается от исчерпанной квоты и так и называется в уведомлении
- Exponential backoff — retry с нарастающей задержкой (60s → 1h)
- Crash recovery — при перезапуске воркера зависшие задачи возвращаются в очередь
- CLI + Web UI — два интерфейса на выбор
- Telegram бот — управление задачами через Telegram с авторизацией по номеру телефона
- Пароль на создание задач в боте — опциональная защита через
PP_TASK_PASSWORD - Автономный режим — флаг на задачу для запуска Claude или Codex без интерактивных подтверждений
- Уведомления — Telegram бот присылает сообщение как только задача завершилась (с результатом или ошибкой)
- Интеграция с herdr — задачи выполняются в живых терминальных сессиях: permission-диалог не роняет задачу, а ждёт подтверждения (с уведомлением в Telegram); режим
keep_paneоставляет сессию открытой для продолжения - herdr → Telegram мост — уведомления о заблокированных/завершённых агентах herdr с кнопками «Подтвердить / Экран / Ответить»
- Опциональная авторизация Web UI/API — токен через
PP_API_TOKEN - Скилы Claude Code — запуск
/skill-nameчерез Web UI и бота (для всех Claude Code провайдеров) - Продолжение сессии — кнопка 💬 в боте после завершённой задачи для диалога в той же сессии
- Пауза воркера — кнопка ⏸ в Web UI и боте, чтобы временно остановить обработку без потери задач
- Автоперезапуск worker'а — при обновлении кода пакета worker сам перезапускается между задачами
- Повторяющиеся задачи — поле Recur:
6h,30m,daily@09:00— новая задача создаётся автоматически, в том числе после неудачного прогона - Экран расписания — кнопка 🗓 в Web UI: все повторяющиеся задачи как серии — период, следующий запуск, исход прошлого и пометка «оборвана»
- Диагностика внешних конвейеров — локальные профили показывают backlog, возраст, churn, ETA и проектные health-check без запуска LLM и расхода токенов
- Правка задачи в очереди — провайдер, модель, эффорт, повтор и приоритет меняются прямо в карточке, без пересоздания задачи
- Фоновый запуск (detached) — запустить процесс в фоне и сразу завершить задачу (для серверов, ботов, polling-скриптов)
- Per-task таймаут — индивидуальный лимит времени задачи в Web UI (переопределяет глобальный
PP_TASK_TIMEOUT) - Свой git worktree на задачу — галка «🌿 свой worktree»: агент работает в отдельном чекауте на ветке
pp/t<id>, твоё рабочее дерево не трогается, результат виден как diff - Срыв среды ≠ провал задачи — отказ доступа (401/403/5xx) или обрыв ответа возвращает задачу в очередь вместо
failed - Сторож запретов — PreToolUse-хук режет катастрофические команды у задач с
--dangerously-skip-permissions: диалога там нет, значит запрет должен держать не он - Параллельные задачи —
PP_CONCURRENCY=N: worker выполняет несколько задач одновременно, но никогда две в одном рабочем дереве - Отмена running-задач — из Web UI, бота или API: worker убивает процесс задачи (группу процессов) в течение пары секунд
- Уведомления об обновлениях — баннер в Web UI когда выходит новая версия
- Дашборд стоимости — статистика расходов за сегодня / неделю / всего по провайдерам
- Дописать решателю — приписка к задаче: пара фраз, которые уйдут в следующий прогон (в том числе в повтор после rate limit)
- Итог задачи —
PP_VERDICT=1: агент заканчивает строкойИТОГ: ГОТОВО | НУЖЕН ЧЕЛОВЕК | НЕ СМОГ | …, и уведомление сразу говорит, надо ли идти смотреть; тихий итогПУСТО(«делать нечего») в Telegram не шлётся — для повторяющихся дежурных задач - Расход за окно лимита —
pp usage: сколько сожжено за последние 5 ч по ВСЕМ сессиям Claude Code, включая herdr-задачи и живую переписку - Tray-приложение — двойной клик на
pp.exe, иконка в трее, всё управление мышью - Standalone .exe — сборка без зависимостей через PyInstaller
- SQLite — данные хранятся локально в
~/.promptpilot/ - Workflow Orchestrator (W3) — planner разбивает большую задачу на утверждаемые этапы, затем автономный цикл «исполнитель → deterministic gate → независимый аудитор» ведёт каждый этап до PASS; есть crash recovery, лимиты, provenance и JSON/Markdown-экспорт для последующего анализа
Архитектура автономного цикла описана в
docs/WORKFLOW_ORCHESTRATOR_SPEC.md.
W0–W3 реализованы: можно импортировать прежние раунды, поручить сильному planner
разбиение новой задачи, отредактировать и один раз утвердить план, после чего
PromptPilot автоматически передаёт ход между executor, gate и reviewer и
сохраняет непрерывный журнал. Настройка описана в
docs/WORKFLOW_AUTOMATION_GUIDE.md.
В Web UI кнопка Workflows → Новый workflow открывает четырёхшаговый мастер:
задача, команда агентов, правила автоматизации и preflight. Перед созданием он
без выполнения проектного кода проверяет Git-путь, имя ветки, доступность
providers и синтаксис gate-команд. Есть рекомендуемый, экономный и усиленный
профили, а повторяющиеся настройки можно сохранить как локальный шаблон.
Экран сразу показывает, у кого «мяч» и какой переход произойдёт дальше.
Минимальный ручной пилот:
pp workflow create workflow.json
pp workflow import-history <id-or-slug> history.json
pp workflow start <id-or-slug> --base-sha <sha>
pp workflow dispatch <id-or-slug> executor --file executor-prompt.md
pp workflow sync <id-or-slug>
pp workflow gate <id-or-slug> PASS --gate-id tests
pp workflow dispatch <id-or-slug> reviewer --file reviewer-prompt.md
pp workflow sync <id-or-slug>
pp workflow review <id-or-slug> PASS --summary "Принято"
pp workflow list
pp workflow show <id-or-slug>
pp workflow rounds <id-or-slug>
pp workflow events <id-or-slug> --json
pp workflow findings <id-or-slug>Формат переноса старых итераций и правила отделения проверенных фактов от
ручных воспоминаний описаны в
docs/HISTORY_IMPORT_GUIDE.md.
- Скачай последний релиз: github.com/ivanarama/PromptPilot/releases
- Распакуй архив
PromptPilot-vX.X.X-windows.zipв любую папку - Заполни
.env(шаблон уже в архиве) - Запусти
start.ps1илиpp.exe tray
git clone https://github.com/ivanarama/PromptPilot.git
cd PromptPilot
pip install -e .Или установка из pip-пакета (из релиза):
pip install promptpilot-X.X.X-py3-none-any.whlТребования: Python 3.10+, хотя бы один AI CLI в PATH (claude, codex, qwen и т.д.).
git clone https://github.com/ivanarama/PromptPilot.git
cd PromptPilot
docker compose up -d --build
# Web UI: http://localhost:8420 (по умолчанию только loopback)docker compose поднимает два сервиса — server и worker — с общей SQLite БД
на volume promptpilot-data (данные переживают пересборку). Образ уже включает
Node.js и @anthropic-ai/claude-code и работает под non-root пользователем.
- Аутентификация Claude в контейнере: проще всего через LiteLLM-прокси —
задайте
ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKENв.envрядом сdocker-compose.yml. Альтернатива для обычной подписки — примонтировать свой логин: добавьте в сервисыvolumes: - ~/.claude:/home/pp/.claude. - Рабочие репозитории: чтобы задачи работали по вашим проектам, примонтируйте
их в контейнер
worker(пример вdocker-compose.yml) и задайтеPP_PROJECTS_ROOT. - Безопасность: порт публикуется только на
127.0.0.1:8420. Чтобы открыть в сеть — поменяйте на"8420:8420"и задайтеPP_API_TOKEN(тогда/api/*требуютAuthorization: Bearer <token>), либо ставьте за reverse-proxy. - Telegram-бот выключен по умолчанию: раскомментируйте сервис
botвdocker-compose.ymlи задайтеPP_TG_TOKEN(при необходимостиPP_TG_PROXY). - Ограничение: Docker-вариант локальный и одномашинный — herdr и раздача задач по ssh на другие машины из контейнера не работают.
./build.sh # → dist/pp
./dist/pp serverИспользует тот же кроссплатформенный pp.spec, что и Windows. Tray-зависимости
(pystray/Pillow) в Linux-сборку не входят — pp tray там просто сообщит об
этом и предложит pp worker/pp server/pp bot.
# Добавить задачу
pp add "Объясни что такое рекурсия"
# Запустить воркер (выполняет задачи)
pp worker
# В другом терминале — запустить веб-интерфейс
pp server
# UI доступен на http://127.0.0.1:8420 (браузер не открывается автоматически)Важно:
pp workerиpp server— два отдельных процесса, которым нужно работать одновременно. Пока воркер не запущен, задачи просто висят вpending.
Двойной клик на pp.exe — иконка появляется в системном трее, worker и server стартуют автоматически.
Правый клик на иконке:
▶ Worker ← кликнуть = остановить
▶ Server ← кликнуть = остановить
■ Bot ← кликнуть = запустить (нужен PP_TG_TOKEN в .env)
─────────────────
Запустить все
Остановить все
─────────────────
Открыть Web UI ← открывает браузер на http://127.0.0.1:8420
─────────────────
Выход ← останавливает все сервисы и закрывает трей
Цвет иконки показывает состояние: 🟢 все работают / 🟠 частично / ⚫ остановлено.
Или явно через команду:
pp traypp worker # запустить воркер
pp server # запустить веб-UI
pp bot # запустить Telegram бот
pp add "промпт" # добавить задачу
pp list # список задач
# и т.д.Оба режима работают с одной и той же БД и настройками.
Все настройки — токен бота, путь к claude.exe, разрешённые номера — хранятся в .env файле.
Скопируй шаблон и заполни:
copy .env.example .env
notepad .env.env рядом с pp.exe (или рядом со скриптом):
PP_TG_TOKEN=7123456789:AAF...
PP_TG_ALLOWED_PHONES=+79001234567,+79007654321
PP_CLAUDE_EXE=C:\Users\YourName\.local\bin\claude.exe
PP_DEFAULT_CLI=claudeАвторизация Claude: PromptPilot запускает
claude.exeкак обычный процесс — он наследует окружение текущего пользователя. Достаточно один раз выполнитьclaude auth loginна этой машине, больше ничего настраивать не нужно.
Порядок поиска .env:
- Рядом с
pp.exe— для дистрибуции - Текущая рабочая директория — для разработки
~/.promptpilot/.env— постоянный пользовательский конфиг
Значения из .env применяются только если переменная не задана в окружении — то есть $env:PP_TG_TOKEN всегда перекрывает .env.
Запустить воркер + сервер в фоне:
.\start.ps1Запустить всё включая Telegram бота:
$env:PP_TG_TOKEN = "ваш-токен"
$env:PP_TG_ALLOWED_PHONES = "+79001234567"
.\start.ps1 -BotЛоги пишутся в .\logs\. Остановить:
.\stop.ps1Скрипт автоматически использует dist\pp.exe если он собран, иначе pp из PATH.
Сборка standalone-бинаря (не требует Python на целевой машине):
.\build.ps1На выходе: dist\pp.exe. Использование аналогично:
.\dist\pp.exe worker
.\dist\pp.exe server
.\dist\pp.exe bot
.\dist\pp.exe add "промпт"Примечание: при первом запуске
pp.exeможет занять несколько секунд — PyInstaller распаковывает бандл во временную папку.
Кириллица и любые не-ASCII промпты безопасны: при не-UTF-8 локали CLI перезапускается в UTF-8 Mode, а битую вставку из терминала (
привет)pp addдетектирует и чинит автоматически.
pp add "промпт" # добавить задачу (дефолтный провайдер)
pp add "промпт" -c codex # через Codex
pp add "промпт" -c qwen # через Qwen
pp add "промпт" -c claude-z # через кастомный алиас
pp add "промпт" -p 1 # с приоритетом (1 = высший)
pp add "промпт" -a "2026-03-25T03:00" # запланировать на время
pp add -f prompts.txt # добавить из файла (по строке)
pp add "промпт" -d /path/to/project # задать рабочую директорию
pp add "промпт" -d /repo -w # в своём git worktree (ветка pp/t<id>)
pp list # задачи (по умолчанию последние 20; -n N — больше)
pp list -s pending # фильтр по статусу
pp status 1 # детали задачи #1
pp cancel 1 # отменить задачу
pp delete 1 # удалить задачу
pp stats # статистика
pp purge --days 7 # удалить старые завершённые задачи
pp note 42 "перечитай комментарий" # дописать решателю к задаче #42
pp note 42 --clear # убрать приписку
pp usage # расход за окно лимита (5 ч)
pp usage --hours 24 --json # то же машинно, за сутки
pp guard --rules # правила сторожа запретов
pp guard "git push origin main" # что сторож сделает с командой
pp worker # запустить воркер
pp server # запустить веб-UI
pp server -p 9000 # на другом порту
pp bot # запустить Telegram бот
- Создай бота через @BotFather, получи токен.
- Задай переменные окружения:
$env:PP_TG_TOKEN = "токен-от-botfather"
$env:PP_TG_ALLOWED_PHONES = "+79001234567,+79007654321"- Запусти:
pp botПри первом открытии бота пользователь видит кнопку «Поделиться контактом». Бот получает номер телефона и сверяет с PP_TG_ALLOWED_PHONES. При совпадении — доступ открыт.
Авторизованные пользователи сохраняются в ~/.promptpilot/tg_users.json. Повторная авторизация при перезапуске не нужна.
Альтернатива env-переменной — файл ~/.promptpilot/tg_config.json:
{
"allowed_phones": ["+79001234567", "+79007654321"]
}| Функция | Описание |
|---|---|
| 📋 Задачи | Список задач с пагинацией и статусами |
🖥 Окна (/windows) |
Живые herdr-сессии по всем машинам: статус, проект, хвост экрана; в карточке — 💬 промпт прямо в панель и клавиши permission-диалога |
| ➕ Добавить задачу | Промпт → провайдер → модель (если у провайдера есть список) → приоритет → skip-permissions → оставить herdr-сессию? (для herdr) → директория → расписание → повтор → режим запуска |
| 📊 Статистика | Сводка по статусам |
| 🔌 Провайдеры | Список провайдеров с деталями: команда, модели, env-переменные (ключи маскированы), источник настройки |
| Детали задачи | Промпт, результат, ошибка; кнопки: отмена (работает и для running — процесс будет убит), сброс, удаление |
| 💬 Ответить | Продолжить диалог с моделью в той же сессии |
⚡ Скилы (/skills) |
Список Claude Code скилов; выбор запускает пошаговое создание задачи |
| 🔔 Уведомления | Автоматически присылает результат или ошибку после завершения задачи |
| 📎 Вложения | Скриншот или файл прямо в мастере: подпись к фото становится текстом задачи |
Файл можно приложить и в боте, и в веб-интерфейсе — агент получит абсолютный путь к нему приписанным к промпту («Приложенные файлы (читай по этим путям)») и прочитает файл сам.
В боте — на шаге ввода промпта или на карточке подтверждения: пришли фото (подпись к нему станет текстом задачи) либо документ. Файлы принимаются и на остальных шагах мастера — альбом из нескольких скриншотов Telegram отправляет отдельными сообщениями, они долетают уже после того, как мастер шагнул дальше. Ограничение Telegram: боту не отдают файлы больше 20 МБ.
В вебе — кнопка 📎, перетаскивание в поле промпта или просто Ctrl+V
скриншота из буфера.
Где лежат файлы: ~/.promptpilot/attachments/<uuid>/<имя> (бот) и
~/.promptpilot/uploads/ (веб). Наружу каталоги не раздаются. Файлы
недосозданной задачи бот удаляет сам, а вложения созданных задач остаются на
диске — автоочистки по возрасту пока нет.
Ограничение: вложения работают только для задач на локальной машине. Файл лежит на этом хосте, и агент на другой машине его не увидит — бот и веб в этом случае не дадут запустить задачу и скажут, почему.
После завершения задачи в деталях появляется кнопка 💬 Ответить — если модель спросила что-то или ты хочешь продолжить диалог:
- Открой детали завершённой задачи → нажми 💬 Ответить
- Введи ответ или следующий вопрос
- Бот создаст новую задачу с флагом
--resume <session_id>— Claude продолжит разговор в том же контексте
Цепочка не ограничена: каждый «ответ» тоже получает кнопку 💬. Новая задача наследует провайдера, рабочую директорию и флаги оригинальной.
При создании задачи через бота последний шаг — выбор режима запуска:
Как запустить?
[▶ Обычно (ждать результата)] [🔁 Фоново (сервер/бот)]
Обычно — воркер ждёт завершения команды и сохраняет результат. Подходит для разовых задач.
Фоново — процесс запускается отдельно и сразу отвязывается. Задача помечается completed (PID XXXX), воркер переходит к следующей задаче. Процесс живёт независимо до ручной остановки.
Когда использовать «Фоново»:
- Запустить другой бот / сервер
- Скрипт с бесконечным polling-циклом
- Любой процесс, который никогда не завершится самостоятельно
Примечание: в режиме «Фоново» воркер не перехватывает вывод и не знает об ошибках после старта. Если процесс упал сразу — в задаче это не отобразится.
Кнопка 🔌 Провайдеры показывает inline-кнопки со списком провайдеров. При нажатии — карточка с деталями:
- Команда запуска (или «Исполнитель: herdr» для herdr-провайдеров)
- Список моделей (или «по умолчанию»)
- Env-переменные (API-ключи маскированы:
sk-...xyz) - Источник настройки (
builtin,providers.json, или оба) и путь к файлу
Если задана переменная PP_TASK_PASSWORD, бот запрашивает пароль перед созданием задачи. При неверном вводе создание отменяется; введённое сообщение автоматически удаляется из чата.
PP_TASK_PASSWORD=mysecretpasswordПросмотр задач и статистика паролем не защищены — только создание.
Встроенные провайдеры:
| Имя | Описание | Скилы | Выбор модели |
|---|---|---|---|
claude |
Claude Code (Anthropic) — дефолт | ✅ | ✅ sonnet / opus / haiku |
claude-z |
Claude Code с альтернативным API (GLM, z.ai и др.) | ✅ | ✅ sonnet / opus / haiku |
codex |
OpenAI Codex | — | — |
qwen |
Qwen Code | — | — |
cursor |
Cursor Agent | — | — |
opencode |
OpenCode AI (GPT-4o/5, o1/o3 и др.) | — | ✅ из списка моделей |
Любой провайдер с
supports_skills=Trueсчитается Claude Code-совместимым и получает выбор модели автоматически.
Команды управления:
pp provider # список всех (+пометки hidden / not installed)
pp provider add <name> ... # добавить
pp provider hide <name> # убрать из списков Web UI и бота (unhide — вернуть)
pp provider remove <name> # удалитьКастомные провайдеры сохраняются в ~/.promptpilot/providers.json. Провайдеры,
чей исполняемый файл не найден на машине, автоматически не показываются в
списках Web UI и бота; ненужные (например claude-z без ключа z.ai) можно
скрыть вручную: pp provider hide claude-z.
Дефолтный провайдер: переменная PP_DEFAULT_CLI (по умолчанию claude).
Путь к claude ищется в PATH автоматически (claude на Linux/macOS,
claude.exe на Windows), переопределяется через PP_CLAUDE_EXE. opencode
ищется в PATH, затем в ~/.opencode/bin (официальный установщик) и npm-биндирах.
pp provider add myai \
--cmd "myai run {prompt}" \
--desc "My AI Tool"С переменными окружения и поддержкой скилов:
pp provider add claude-z `
--cmd "C:\Users\<username>\.local\bin\claude.exe -p --verbose --output-format stream-json {prompt}" `
--desc "Claude Code (GLM via z.ai)" `
--env "ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic" `
--env "ANTHROPIC_AUTH_TOKEN=your-token-here" `
--env "ANTHROPIC_DEFAULT_SONNET_MODEL=glm-4.7" `
--env "ANTHROPIC_DEFAULT_OPUS_MODEL=glm-4.7"Windows:
subprocessне видит PowerShell-функции и алиасы — нужен полный путь к исполняемому файлу..cmd/.bat-обёртки (npm-инструменты вродеqwen,codex) находятся автоматически черезshutil.which.
herdr — терминальный мультиплексор для
AI-агентов. PromptPilot умеет выполнять задачи не headless-процессом, а в
живой herdr-сессии: агент виден, к нему можно подключиться и вмешаться,
а главное — задача, упёршаяся в permission-диалог, не падает и не требует
--dangerously-skip-permissions: агент переходит в состояние blocked,
в Telegram приходит уведомление, вы подтверждаете действие — задача
продолжается.
Провайдер с исполнителем herdr описывается так (~/.promptpilot/providers.json):
"claude-herdr": {
"executor": "herdr",
"kind": "claude",
"description": "Claude Code в herdr-сессии",
"supports_skills": true
}Добавить можно тремя способами:
Web UI — кнопка ⚙ Providers → форма «Добавить провайдер» → тип «herdr-сессия» → указать имя, агента (kind) и, при желании, список моделей через запятую — тогда при создании задачи появится выпадашка выбора модели.
CLI:
pp provider add claude-herdr --executor herdr --kind claude --desc "Claude в herdr"
pp provider add opencode-herdr --executor herdr --kind opencode \
--desc "OpenCode в herdr" \
--models "opencode/big-pickle,opencode/mimo-v2.5-free,opencode/deepseek-v4-flash-free"providers.json — вручную, поле "models": [...] опционально.
kind — любой агент, который поддерживает herdr: claude, codex, gemini,
cursor, opencode, grok, copilot, droid, amp, kilo и другие
(полный список: herdr agent start --help). Список доступных моделей opencode:
opencode models. Выбранная модель передаётся агенту флагом --model при
старте сессии.
Режимы завершения:
- «🖥 оставить сессию открытой» — галочка при создании задачи (Web UI;
в боте — отдельный шаг для herdr-провайдеров), по умолчанию включена:
результат сохраняется в базу, но сессия остаётся — приходите, читаете
транскрипт и продолжаете диалог в той же панели. Снятая галочка — панель
закрывается после задачи. (Провайдерный флаг
"keep_pane": trueтоже поддерживается и форсирует режим.) - detached (галочка «Фоновый запуск» в боте) — промпт отправлен, задача сразу completed, агент работает в открытой панели.
Ограничения: интерактивная сессия не отдаёт stream-json, поэтому cost/session_id для herdr-задач не считаются (дашборд стоимости и кнопка 💬 неактивны).
Встроенный провайдер herdr-session отправляет промпт в уже открытую
панель herdr — оставленную задачей (pp-kept-N) или запущенную вручную:
- Web UI: выберите провайдера
herdr-session— появится выпадашка живых сессий (панель · агент · статус · имя); в боте — те же кнопки. - Работает вся механика задач: очередь, расписание («в 09:00 подведи итоги»), recurrence, blocked-уведомления, отмена, результат в базу.
- Чужая панель никогда не закрывается и не переименовывается; если панель закрыли до запуска — задача падает с понятной ошибкой (и уведомлением).
- Так удобно строить цепочки продолжений в одну сессию — аналог «💬 Ответить» для herdr-задач.
Обратная сторона моста: не ждать уведомления, а самому зайти и посмотреть, чем
заняты агенты. Список живых панелей собирается по всем машинам с herdr
(agent list), в карточке — статус, проект, хвост экрана и действия:
- 💬 Ответить — промпт уходит прямо в панель (
agent prompt), мимо очереди задач: быстро, но без записи в задачи и без учёта стоимости. - Enter / 2 / 3 / Esc — ответ на permission-диалог, когда агент
blocked. - Панели задач PromptPilot помечены номером задачи (
#42).
Отправленные так промпты журналируются в таблицу prompt_log — время, проект
(cwd панели), машина, панель и текст; сами транскрипты не дублируются, их
хранит агент (~/.claude/projects/…). Выключается через PP_LOG_PROMPTS=0.
У запущенной herdr-задачи раскрытая карточка показывает блок Экран агента: хвост панели (обновляется раз в 4 секунды, пока карточка раскрыта), статус агента и кнопки Enter / 2 / 3 / Esc — тот же ответ на permission-диалог, что в боте, но с десктопа. Раньше заблокированная задача выглядела в вебе как бесконечный «running now», и разблокировать её можно было только из Telegram или из самого herdr.
Эндпоинты сознательно узкие: GET /api/tasks/{id}/screen и
POST /api/tasks/{id}/keys работают только через id задачи (не через
произвольный pane_id), а список клавиш — ровно enter, 2, 3, esc.
Произвольного ввода в чужой терминал через веб нет.
Web UI: ⚙ Providers → «Изменить» у нужного провайдера — форма заполнится текущими значениями; сохранение с тем же именем перезаписывает. У встроенных провайдеров так создаётся переопределение (сбрасывается кнопкой «Сбросить к встроенному»). Замаскированные env-секреты при редактировании нужно ввести заново.
Повторяющаяся задача хранится как постоянная серия, а каждый запуск — отдельное вхождение этой серии. Кнопка 🗓 Расписание в Web UI показывает период, параметры (провайдер, модель, эффорт, машина), следующий запуск, результат прошлого прогона и статистику здоровья. Кнопка «Изменить» правит всю серию: основной интервал, effort, приоритет и таймаут сохраняются для будущих запусков, даже если текущий уже работает.
Для временного разбора накопившейся очереди есть «ускорение»: например,
основной интервал 4h, временный 1h. PromptPilot автоматически вернёт 4h
по указанной дате или после N последовательных итогов ПУСТО. Серия также
ставится на паузу, возобновляется, запускается немедленно или окончательно
завершается из Web UI и Telegram (/schedule). При сокращении интервала уже
созданный будущий запуск переносится ближе сразу; ждать старого четырёхчасового
scheduled_at больше не нужно. Более длинный интервал, наоборот, не откладывает
запуск, который уже должен состояться раньше.
В общей очереди карточка повторяющейся задачи показывает короткое имя серии, а
полный служебный промпт остаётся внутри раскрытой карточки. Если серия поставлена
на паузу, будущий запуск явно помечается PAUSED и не выглядит как готовый к
исполнению queued.
Отдельная кнопка «📈 Конвейеры» в главной панели и команда бота /pipeline
показывают любой настроенный pipeline-профиль. Аналитика считает внешний backlog
через GitHub CLI, сопоставляет его с ёмкостью одного прогона и сохраняет локальные
снимки по profile_id. Узкое место — этап с максимальным ETA: учитываются
backlog / capacity, интервал серии и средняя длительность её выполнения.
PromptPilot не поставляет профили конкретных проектов. Метки, запросы и лимиты
задаются пользователем в ~/.promptpilot/pipeline_profiles.json (или файле из
PP_PIPELINE_PROFILES), поэтому чужие репозитории не появляются в новой установке.
Если в профиле включён priority_control, дашборд показывает элементы очередей
и позволяет поставить P0…P3 либо вернуть автоматическую оценку. Кнопка
«⚡ Следующим» ставит P0 и переносит будущий запуск соответствующей серии на
ближайшее время; поставленная на паузу серия при этом остаётся на паузе.
Одинаковые диагностические сигналы в окне конвейера сворачиваются в одну строку
со списком и количеством PR; владелец single-flight барьера всегда показывается
первым, чтобы было видно, какой PR сейчас должен пройти REVIEW → MERGE. В основной
карточке служебные коды и сырая health-сводка заменяются понятными формулировками:
какой PR следующий, выполняется ли REVIEW прямо сейчас, сколько решений ждут
человека и сколько PR ещё переходят со старого формата протокола.
Снимки активных профилей собираются фоново каждые пять минут
(PP_PIPELINE_SNAPSHOT_INTERVAL, 0 отключает). Дашборд показывает Δ backlog,
вход/выход, переходы между очередями, churn, возраст элементов и смысловые итоги
прогонов за 5 и 24 часа. Это обычные GitHub API, SQLite и арифметика — LLM не
вызывается и токены не расходуются. После первого запуска историческим карточкам
нужно накопить соответствующее окно; до этого интерфейс честно показывает покрытие.
В таблице каждого этапа видны номер, время и итог последнего завершённого запуска;
отдельная карточка показывает время последнего REVIEW. Для Codex PromptPilot
использует JSONL-режим CLI и сохраняет точные input/output tokens. Старые
текстовые запуски остаются с пометкой «нет данных»: их расход не оценивается по
длительности и не выдумывается задним числом. «ГОТОВО без действий» учитывается
отдельно от продуктивных прогонов.
Идентификатор JSONL-сессии сохраняется сразу после события thread.started, а
не только в финале. Если worker или процесс Codex оборвался после создания
ветки/worktree, retry запускает codex exec resume для той же сессии. Благодаря
этому продолжение знает о собственных незавершённых изменениях и не объявляет их
«чужой работой» после повторного запуска.
stop.ps1 не полагается только на .pp-pids.json: после hot-reload PID worker
меняется. Скрипт получает актуальный worker PID из heartbeat API, server PID —
из слушателя PP_PORT, проверяет командную строку процесса и лишь затем
останавливает дерево. Поэтому штатный stop не оставляет скрытый worker, который
продолжает расходовать токены после «остановки» старого launcher PID.
Рекомендация считается прозрачно: backlog / capacity даёт число необходимых
прогонов, а один цикл равен интервал + средняя длительность прогона — повтор
назначается после завершения предыдущего запуска. Поэтому серия 15m со средним
выполнением 30 минут даёт примерно один результат за 45 минут, а не четыре в час.
Профиль задаёт желаемое время очистки (target_clear_hours, обычно 8 часов),
интервал выбирается из практичных пресетов. Если одна длительность прогонов уже
не позволяет уложиться в цель, дашборд предлагает увеличить ёмкость или
параллелизм. ETA — нижняя оценка: она не учитывает новые входящие задачи и
ожидание более приоритетных серий в общей очереди worker; для этапа с
manual_gate: true ускорение не предлагается, потому что его ограничивает
решение человека.
Worker публикует heartbeat в общей SQLite БД. Активный профиль немедленно
становится красным, если heartbeat отсутствует или устарел; running, который
пережил настроенный task_timeout, отдельно показывается как зависший запуск.
Это позволяет отличить медленную очередь от остановившегося исполнителя без
ожидания пятичасового окна статистики.
Каждая строка расписания — самостоятельная серия со своим working_dir.
Аналитический профиль объединяет нужные серии в одну логическую цепочку по
series_contains. Несколько проектов — это несколько профилей; раздел
«Конвейеры» покажет их в выпадающем списке и не смешивает снимки. Пользовательский файл по умолчанию:
~/.promptpilot/pipeline_profiles.json:
{
"profiles": {
"my-project": {
"title": "MyProject: сопровождение",
"repository": "owner/my-project",
"target_clear_hours": 8,
"priority_control": {
"trusted_account": "YOUR_GITHUB_LOGIN",
"default_level": "p2",
"aging_hours": 168,
"max_items": 12
},
"queues": [
{
"id": "fix",
"title": "Исправления",
"query": "is:issue is:open label:ready-fix -label:in-work",
"capacity": 1,
"series_contains": "MyProject - FIX",
"backlog_diagnostic_field": "fix_candidates",
"wake_when": {"field": "fix_candidates"},
"dispatch_gate": {
"skip_when_empty": true
}
},
{
"id": "review",
"title": "Ревью",
"query": "is:pr is:open -label:ship -label:changes-requested -label:needs-decision -label:hold",
"capacity": 2,
"series_contains": "MyProject - REVIEW",
"backlog_diagnostic_field": "review_backlog",
"wake_when": {"field": "review_candidates"}
},
{
"id": "merge",
"title": "Слияние",
"query": "is:pr is:open label:ship",
"capacity": 1,
"series_contains": "MyProject - MERGE",
"backlog_diagnostic_field": "merge_candidates",
"wake_when": {"field": "merge_candidates"},
"dispatch_gate": {
"skip_when_empty": true,
"defer_when_diagnostics_match": [{
"field": "review_candidates",
"key": "stage",
"values": ["integration-review", "legacy-integration-review"]
}],
"defer_for": "10m"
}
}
],
"health_check": {
"command": ["/absolute/path/to/project-health", "-json"],
"working_dir": "/absolute/path/to/project",
"timeout_seconds": 180
}
}
}
}priority_control необязателен. Он использует GitHub-метки queue:p0…
queue:p3 для ручного решения и queue:auto:p0…queue:auto:p3 для оценки,
перенесённой с issue на PR. Ручная метка старше автоматической; без обеих
critical/security получает P0, bug — P1, enhancement/documentation/default —
P2, question — P3. Каждые aging_hours ожидания эффективный уровень повышается
на один до P1, поэтому низкий приоритет не означает вечное ожидание, а P0
остаётся полосой для действительно срочной работы. Если
проектный исполнитель сортирует кандидатов сам, он должен применять те же
правила: PromptPilot управляет метками и показывает порядок, но не подменяет
безопасный выбор внутри репозитория. Проверка trusted_account не даёт случайно
менять приоритет под другой учётной записью GitHub CLI.
health_check необязателен. Это проектная команда без shell, которая должна
вернуть JSON со state: green|yellow|red, summary и массивом findings.
PromptPilot запускает её при каждом снимке и показывает результат сразу, поэтому
нарушение текущих инвариантов видно без ожидания 5- или 24-часовой истории. Команда
не должна вызывать LLM; для GitHub-проверок путь из PP_GH_EXE передаётся ей как
GH_EXE. Таймаут по умолчанию — 180 секунд; для больших репозиториев его можно
увеличить до 900. Ошибка или timeout самой команды отображается как
«диагностика не выполнена», а не как доказанное нарушение инварианта.
dispatch_gate тоже необязателен и настраивается у конкретного этапа. Перед
запуском провайдера PromptPilot получает свежий снимок профиля. При
skip_when_empty пустой запуск завершается как ПУСТО, не запуская LLM. Поле
defer_when_diagnostics_nonempty задаёт простую зависимость от непустого массива
в JSON health_check. Когда один массив содержит несколько состояний, используйте
defer_when_diagnostics_match: в примере MERGE возвращается в pending только для
элементов со stage integration-review/legacy-integration-review, но проходит
для integration-merge-ready. Это даёт автоматический порядок REVIEW → MERGE
даже при более высоком приоритете MERGE и не расходует токены на ожидание. Если
профиль или checker сломан, gate fail-open: задача запускается обычным способом,
чтобы ошибка наблюдаемости не остановила полезную работу.
wake_when необязателен. Он указывает поле диагностики, которое означает, что
этапу уже есть что делать. После любого полезного (ГОТОВО) запуска и при
фоновом обновлении профиля PromptPilot переводит ближайший pending-запуск этой
серии на «сейчас». Поэтому FIX → REVIEW и REVIEW → MERGE не ждут следующего
периодического интервала. Можно добавить key и values, если готовность
определяется только некоторыми состояниями элементов. При ПУСТО, паузе серии
или пустом поле пробуждения нет.
backlog_diagnostic_field заменяет приблизительный размер GitHub Search точным
массивом из health_check. Это особенно важно для REVIEW: поисковый запрос
намеренно не исключает stale-метку reviewed, а checker уже различает новый
HEAD, актуально проверенный HEAD и интеграционный handoff. Для REVIEW используйте
review_backlog (вся ожидающая работа), а wake_when оставьте на
review_candidates (только исполняемая сейчас работа).
Не исключайте reviewed из поискового запроса REVIEW: метка относится к старому
HEAD и после нового push может остаться. Канонический health_check отличает
актуальное ревью (reviewed_waiting_ship) от устаревшей метки и возвращает новый
HEAD в review_candidates. Для двухполосной схемы он также публикует
content_review_candidates, integration_owner и merge_candidates:
single-flight сериализует интеграцию, но не останавливает содержательные ревью.
Для REVIEW/MERGE повторяемые GitHub-проверки можно вынести из промпта в проектный CLI. PromptPilot не привязан ни к Claude, ни к Codex: он сам выполняет безопасный preflight до запуска провайдера и только затем решает, нужна ли модель. В очередь профиля добавляется:
"execution": {
"mode": "auto",
"stage": "review",
"command": [
"{python}", "-m", "promptpilot.project_pipeline",
"--config", "pipelinectl.json", "next", "{stage}"
],
"required_paths": ["pipelinectl.json"],
"probe_command": [
"{python}", "-m", "promptpilot.project_pipeline",
"--config", "pipelinectl.json", "capabilities"
]
}{python} заменяется интерпретатором запущенного PromptPilot, {stage} — id
этапа. Команда и относительные пути проверяются в working_dir серии без
вызова LLM, после чего command выполняется с таймаутом timeout_seconds
(по умолчанию 180 секунд). Режимы:
| Режим | Поведение |
|---|---|
auto |
empty/wait завершается без модели; audit/merge получает короткий prompt с готовым lease; fallback или недоступный CLI использует исходный prompt/скилл |
tool |
CLI обязателен; при отсутствии, ошибке или fallback запуск завершается НУЖЕН ЧЕЛОВЕК без токенов |
skill |
всегда используется исходный prompt, как до появления pipelinectl |
Эффективный маршрут (auto → tool, auto → skill или blocked) виден в
дашборде и боте для каждого этапа. Это позволяет заметить незапланированный
fallback сразу, а не по суточному расходу токенов. Пустой REVIEW/MERGE
закрывается самим preflight с явной записью «провайдер не запускался».
Встроенный promptpilot.project_pipeline реализует общий protocol v1. Проект
задаёт repository, доверенный аккаунт, health-команду, base branch и required
checks в pipelinectl.json. Обычный REVIEW получает ровно один кандидат и
opaque lease; complete review повторно проверяет HEAD и два одинаковых полных
GraphQL snapshot, затем сам выполняет review → claim → label → completion.
Обычный CLEAN MERGE аналогично повторяет proof/labels/CI и использует merge с
точным SHA. Base-sync, carry, legacy re-ship, конфликт, recovery и третий круг
намеренно возвращают action=fallback: их продолжает полная проектная
процедура. Таким образом быстрый путь не ослабляет сложные гейты.
Claude и Codex получают одну и ту же команду и JSON. Различаются только тонкие
файлы обнаружения навыка (.claude/skills и .agents/skills); логика CLI и
lease от модели не зависят.
Статус в Web UI и боте читается так:
| Статус | Значение |
|---|---|
green — «работает штатно» |
Инварианты соблюдены, активных ожиданий нет |
yellow — «есть ожидания» |
Выполняется восстанавливаемая транзакция или нужен ход человека; это не поломка |
red — ошибка |
Worker не работает, запуск превысил timeout, серия оборвана либо нарушен проектный инвариант |
Причина и конкретные findings выводятся рядом со статусом. Поэтому жёлтый
индикатор сам по себе не требует останавливать очередь: сначала нужно прочитать,
какое именно ожидание он показывает.
Жёлтый статус означает ожидаемый handoff или ход человека; подробная причина видна в той же карточке.
У двух цепочек могут быть одинаковые этапы: добавьте проект в первую строку
промпта (MyProject - REVIEW, OtherProject - REVIEW) и используйте такое же
уникальное значение в series_contains. Профиль влияет только на аналитику;
интервалы, effort, пауза и временное ускорение принадлежат самим сериям.
Сейчас профиль и серии связываются по названию. Отдельного мастера «Создать всю цепочку из шаблона» пока нет: этапы создаются как повторяющиеся задачи, а потом собираются профилем. Это важное отличие от Workflow-оркестратора: workflow ведёт конечную разработку «исполнитель → аудитор → исправление», а расписание обслуживает бесконечные дежурные очереди TRIAGE/FIX/REVIEW/MERGE.
Серия без запланированного вхождения помечается «оборвана» — она сама не
продолжится. Раньше в это состояние приводило любое падение: следующее
вхождение создавалось только после успешного прогона, поэтому один rate limit
тихо убивал дежурную задачу насовсем. Теперь расписание продлевается и после
failed, а в Telegram уходит «🔁 задача упала, но расписание продолжено —
следующий запуск в …». Отменённая серия не продлевается: отмена — это решение
человека, а не сбой.
Одно вхождение в очереди (pending/rate_limited) по-прежнему можно править
в карточке. Для постоянного изменения расписания используйте экран серии.
-
Claude Code и Codex — уровень
low|medium|high|xhigh|maxзадаётся полем и работает на двух уровнях:- у провайдера — «Эффорт рассуждений» в форме провайдера (⚙ Providers)
или
pp provider add ... --effort max. Это дефолт: с ним живут все задачи провайдера, в том числе этапы конвейеров; - у задачи — селект «Effort» рядом с «Model» в веб-мастере и кнопка «Эффорт» в разделе «Дополнительно» бота. Перекрывает провайдера на один прогон и наследуется следующими вхождениями повторяющейся задачи.
Пустое значение = «по умолчанию»: PromptPilot не передаёт override и уровень выбирает сам CLI. Для Claude используется
--effort, для Codex — одноразовый-c model_reasoning_effort=...; глобальная конфигурация агента не меняется. Если настройка уже вписана руками в шаблон команды или аргументы провайдера, выигрывает написанное руками. Остальным агентам effort не передаётся. - у провайдера — «Эффорт рассуждений» в форме провайдера (⚙ Providers)
или
-
OpenCode — два пути:
-
headless: флаг
--variant high|max|minimalвcmd-шаблоне (opencode run --variant max {prompt}); -
herdr-сессия: у TUI флага запуска нет, но variant задаётся через агент-профиль в
~/.config/opencode/opencode.jsonc:{ "agent": { "max": { "mode": "primary", "variant": "max" } } }и доп. аргументы провайдера
--agent max.
-
Заводить отдельных провайдеров под каждый уровень (claude-herdr и
claude-herdr-max) больше не нужно — но если такие уже есть, они продолжают
работать: эффорт провайдера просто становится их дефолтом.
Побочно: cmd-провайдер, собранный вокруг самого claude (клон встроенного
через «Изменить»), теперь отмечается как Claude-провайдер автоматически — ему
достаются скилы, выбор модели, --resume и эффорт. Раньше такой клон выглядел
«неизвестным CLI» и молча ронял всё перечисленное.
Минимальный плагин для herdr 0.7.5+ лежит в herdr-plugin/. Установка:
herdr plugin install ivanarama/PromptPilot/herdr-plugin # из GitHub
herdr plugin link /path/to/PromptPilot/herdr-plugin # локальная копияПлагин — тонкая обёртка над pp: сам PromptPilot он не ставит и без него не
работает. Если pp на машине нет (не в PATH, не в ~/.local/bin, модуль
promptpilot не импортируется), плагин не пытается запустить что-то наугад —
он показывает уведомление herdr со ссылкой на установку и пишет то же самое в
~/.promptpilot/startup.log.
Что даёт:
- автозапуск worker'а — при старте herdr-сервера плагин поднимает
pp worker, если тот ещё не запущен (уже работающий не трогается); - постановка задачи из панели — action «PromptPilot: поставить задачу»
(палитра действий,
herdr plugin action invoke enqueue --plugin promptpilotили клавишаctrl+alt+e) открывает popup: ввёл текст — задача ушла в очередь с рабочей директорией текущей панели.
После правок манифеста нужен herdr plugin unlink promptpilot + повторный
link.
По умолчанию агент работает прямо в указанной директории — то есть в том же
рабочем дереве, где сидишь ты. Галка 🌿 свой worktree (Web UI, шаг мастера
в боте, pp add -w, поле worktree: true в API) меняет это: задача получает
собственный чекаут репозитория на ветке pp/t<id>.
Что это даёт:
- твоё рабочее дерево остаётся нетронутым — незакоммиченные правки в безопасности, ветка не переключается;
- результат — ветка, а не грязный
git status: смотришь diff, вливаешь или выбрасываешь целиком; - retry после rate-limit возвращается на ту же ветку и в тот же чекаут, а не наслаивается на полуизменённое дерево;
- две задачи по одному репозиторию могут идти параллельно (см.
PP_CONCURRENCY).
Куда попадает чекаут:
| Исполнитель | Расположение |
|---|---|
| herdr-провайдеры | herdr создаёт worktree-workspace сам (herdr worktree create) — его видно в UI herdr, там же можно закрыть или удалить. Работает и на удалённой машине |
| обычные (headless) | <родитель репозитория>/.pp-worktrees/<repo>-t<id>, либо PP_WORKTREES_ROOT/<repo>/t<id> |
Путь и ветка сохраняются в задаче и показываются в Web UI, боте и pp status <id>
— вместе с короткой сводкой «коммитов: N, незакоммиченных файлов: M».
Нюансы:
- Директория обязана быть git-репозиторием. Не репозиторий — галка в Web UI недоступна, шага в боте нет, а задача, созданная через API, честно падает с ошибкой вместо тихого запуска в общем дереве.
- Игнорируемые файлы не переезжают. Свежий чекаут — без
.env,node_modules,venv. PromptPilot копирует в него то, что перечислено вPP_WORKTREE_COPY(по умолчанию.env) — и только те файлы, которые git действительно игнорирует. Остальное — сборка, установка зависимостей — на совести самой задачи. - Чекаут не удаляется после задачи — в нём результат. Исключение: если
агент не оставил ни коммитов, ни изменений, herdr-исполнитель убирает пустой
чекаут за собой. Ветка
pp/t<id>остаётся всегда. - На удалённых машинах worktree поддерживают только herdr-провайдеры: headless-команда по ssh выполняется в домашней директории и в чекаут просто не зайдёт — такая задача падает с явным сообщением.
PP_CONCURRENCY=1 (по умолчанию) — worker берёт задачи строго по одной, как
раньше. Больше единицы — задачи идут в пуле потоков, и очередь при этом
обходит всё, что столкнулось бы с уже работающей задачей:
- две задачи с одной рабочей директорией (на одной машине) одновременно не запустятся — второй агент подождёт, пока первый закончит;
- задачи со своим worktree не блокируют никого и ничего: у каждой свой чекаут — именно в этом сочетании параллелизм и раскрывается;
- задачи в одну и ту же открытую herdr-сессию (
herdr-session) сериализуются по сессии.
Захват задачи атомарный (BEGIN IMMEDIATE + условный UPDATE), так что гонки
за одну задачу нет. Предположение остаётся прежним: worker'ов — один
процесс; второй при старте вернёт running-задачи первого в очередь
(recover_running). Нужно больше параллелизма — поднимай PP_CONCURRENCY, а
не второй worker.
Раньше любой ненулевой код возврата означал failed. Но отказ в доступе
(API Error: 401/403/5xx, Failed to authenticate) и обрыв ответа на полуслове
(Connection closed mid-response, terminal_reason=api_error, ECONNRESET,
EAI_AGAIN) — это не вина задачи. Причём обрыв почти всегда приходится на конец
прогона: работа сделана и закоммичена, не доехало только последнее слово.
Теперь такая задача возвращается в очередь со статусом rate_limited и
обычным экспоненциальным backoff'ом, а в error пишется, что именно случилось.
Считается это против max_retries, так что навсегда сломанная среда всё-таки
доводит задачу до failed, а не крутит её вечно.
Что НЕ считается срывом среды: API Error: 400, падения тестов, отсутствующие
модули, ошибки компиляции — всё это провал задачи и честный failed. Коды
сокетов (ENOTFOUND и прочие) сверяются с учётом регистра: иначе
ModuleNotFoundError читался бы как обрыв связи.
PP_MIN_FREE_MB (по умолчанию 0 — проверки нет): если свободной памяти меньше,
worker не берёт новую задачу и говорит об этом один раз, а не каждый опрос.
Слоты PP_CONCURRENCY сами по себе ничего не знают о том, потянет ли машина ещё
один прогон — а на деле она уходит в своп, и следом API начинает отказывать. На
Linux читается MemAvailable, на Windows — GlobalMemoryStatusEx; там, где
померить нельзя, очередь не останавливается.
Прогон идёт, а видно, что копает не туда — не перечитал свежий комментарий, работает по старому описанию. Приписка к задаче добавляет пару фраз, которые пойдут в следующий прогон отдельным блоком после промпта, с прямым указанием, что это написано последним и главнее всего выше.
pp note 42 "перечитай комментарий, идёшь не туда"
pp note 42 # показать
pp note 42 --clear # убратьВ Web UI — поле «Дописать решателю» в развёрнутой карточке задачи; в списке у
такой задачи стоит пометка ✎ приписка.
Живёт при задаче, а не при прогоне — и это главное:
- идущий прогон её уже не увидит — она уйдёт в следующий прогон (после rate limit или срыва среды); если нужно применить прямо сейчас, проще пересоздать задачу с уже вписанной припиской;
- одноразовая: задача дошла до вердикта — приписка снимается, чтобы не лезть во все следующие прогоны;
- но срыв по вине среды или rate limit её сохраняет — та попытка приписку так и не увидела;
- в повторяющиеся копии задачи она не попадает: следующая копия создаётся из сохранённого промпта, а не из того, что получил конкретный прогон.
PP_VERDICT=1 дописывает к промпту просьбу закончить одной строкой:
ИТОГ: ГОТОВО | УЖЕ СДЕЛАНО | НУЖЕН ЧЕЛОВЕК | НЕ СМОГ | ПУСТО
Итог парсится и хранится у задачи, показывается в Web UI, боте и pp status.
Разница практическая: код возврата говорит только «процесс завершился», а
🟡 НУЖЕН ЧЕЛОВЕК в уведомлении сразу говорит, надо ли идти смотреть.
ПУСТО — тихий итог для повторяющихся задач: «проснулся по расписанию, делать
нечего». Такая задача завершается как обычно (итог в базе, виден в списках),
но уведомление в Telegram не отправляется — иначе дежурный робот,
просыпающийся каждые два часа, превращает бота в будильник. Ошибки (failed)
шлются всегда, независимо от итога.
Парсится он всегда, даже когда мы не просили — если агент сам закончил такой строкой, итог подхватится. Побеждает последнее совпадение: формат могли процитировать по дороге, а вердикт — это закрывающая строка.
Каждый прогон помечен в своём окружении переменной PP_TASK_ID, и метку
наследует процесс агента. Поэтому живой прогон опознаётся по процессу, а не
по нашим же записям — и переживает смерть worker'а.
При старте recover_running() возвращает в очередь зависшие running-задачи, но
теперь обходит те, чей агент всё ещё работает: раньше перезапуск worker'а
выдёргивал очередь из-под живых агентов, а второй процесс worker'а на старте
отбирал задачи у первого. Поиск идёт по /proc (Linux); там, где так нельзя,
поведение прежнее.
Дашборд стоимости считает деньги по результатам задач — а herdr-задачи стоимости не дают вообще: интерактивная сессия не отдаёт stream-json. Чем больше работы уходит в herdr, тем слепее был дашборд.
pp usage (и строка «Окно 5ч» в Web UI) закрывает дыру, разбирая транскрипты,
которые Claude Code пишет в ~/.claude/projects/**/*.jsonl — в том числе для
herdr-панелей:
За последние 5 ч — оценка по прайсу API:
всего: $18.40 26.1 млн токенов сессий: 4
задачи: $12.05
прочее: $6.35 (живая переписка ест то же окно лимита)
Почему именно так:
- Считаем по сообщениям, а не по кускам. В транскрипте каждое сообщение
записано целиком и с полным
usage— суммировать можно. В потоковом журнале headless-прогона наоборот: сообщение разбито на куски с частичным usage, и суммирование занижает выход в сотню раз. Поэтому для headless-задач по-прежнему берётся готовый итог из событияresult. - Лимит общий на человека, поэтому в счёт идут и твои собственные сессии — иначе на вопрос «сколько осталось» ответить нельзя.
- Задача узнаётся по
session_id, а если его нет — по своему worktree. Обычныйworking_dirв признаки не годится: его задача делит с человеком, и живая переписка засчитывалась бы задаче (проверено — так и было). - Кэш считается отдельно: чтение ×0.1 от входа, запись ×1.25. Без этого счёт мимо на порядок — кэш-чтений на порядок больше обычного входа.
- Деньги — оценка по прайсу API, а не счёт. На подписке они не списываются; полезны как мера того, куда уходит окно.
Окно скользящее — «последние 5 часов», а не доля выбранной нормы: точку сброса API называет только событием лимита, которого в транскриптах нет.
Задача с --dangerously-skip-permissions не спрашивает человека ни о чём —
значит, и остановить её диалогом нельзя. Останавливает PreToolUse-хук: он
видит команду до запуска и срабатывает независимо от режима разрешений.
Заблокированное — код 2 и причина в stderr, её читает модель; всё
заблокированное пишется в ~/.promptpilot/guard.log.
По умолчанию (PP_GUARD=auto) сторож включается ровно там, где больше некому
спросить: у задач с skip_permissions, и только для Claude Code провайдеров —
формат хука их. PP_GUARD=1 — всегда, PP_GUARD=0 — никогда.
Что запрещено «из коробки»: удаление корня и домашнего каталога, удаление
.git, форс-пуш и удаление веток на сервере, пуш в main/master (в том
числе git push origin HEAD из main — ветка проверяется отдельно, регулярка не
видит, что именно пушат), git worktree remove|prune (в чужих worktree идёт
работа других задач), sudo, запись на устройства, выключение машины.
Правила намеренно узкие: срабатывать они должны на катастрофе, а не на слове
sudo внутри сообщения коммита — поэтому команды опознаются по позиции в
строке, а main-fix не считается веткой main. Посмотреть и проверить:
pp guard --rules # какие правила действуют
pp guard "git push origin main" # что будет с этой командой (ничего не запускается)Дополнить или заменить — ~/.promptpilot/guard.json:
{"extend": [{"pattern": "npm\\s+publish", "reason": "Публикация — только руками"}]}{"replace": [...]} вместо extend отключает встроенные правила целиком.
Сломанный или нечитаемый файл оставляет встроенные правила в силе — сторож,
который сам себя разоружает из-за лишней запятой, хуже отсутствующего.
Ограничение: на удалённых машинах сторож не ставится — файл настроек с хуком лежит здесь, а путь к нему там ничего не значит.
Машина — отдельное измерение задачи: регистрируете машины, а те же самые провайдеры работают на любой из них.
Web UI: ⚙ Providers → «🖥 Машины» → имя + user@host → «Добавить и
проверить» — PromptPilot сам определяет по ssh, какие CLI есть на машине
(login-shell PATH). После этого в форме задачи появляется поле Машина
(💻 локально / vm2 / ...), и список провайдеров фильтруется по возможностям
выбранной машины. В боте — тот же шаг «Где выполнить задачу?».
Как выполняется: ssh host bash -lc '<команда провайдера>' — исполняемый
файл ищется в PATH удалённой машины, env-блок провайдера (например,
claude-z с GLM) пробрасывается, stream-json проходит насквозь — стоимость,
session_id и rate-limit-детекция работают как локально.
Требования: ssh по ключу (BatchMode), нужные CLI на машинах (и их PATH в
~/.profile). Ограничение: detached-задачи (фоновый запуск headless-процесса)
— только локально; headless-команда выполняется в домашней директории
удалённой машины.
Низкоуровневая альтернатива — обёртка scripts/pp-ssh-run <host> <cmd...> {prompt} как cmd-шаблон провайдера (полный контроль над командой и путями).
herdr-провайдеры (включая herdr-session) работают на машинах так же, как
локально: PromptPilot вызывает herdr CLI удалённой машины по ssh, а панель с
агентом живёт там — подключиться к ней можно командой
herdr --remote <host> (она есть в уведомлениях и в мета-строке результата).
- Проба машины сама находит herdr-провайдеров: нужен
herdrв login-shell PATH машины и CLI соответствующегоkind(claude,opencode, ...). Если агент установлен не в системный PATH (например, opencode в~/.opencode/bin), допишите путь в~/.profileмашины и нажмите «Перепроверить» — иначе провайдер в списке машины не появится. - herdr server на машине поднимается автоматически (
herdr serverв фоне), если он там ещё не запущен. - Работают все режимы: blocked-ожидание с уведомлением и кнопками, keep-pane,
detached,
herdr-session(выпадашка живых сессий показывает сессии выбранной машины). - Рабочая директория задачи должна существовать на целевой машине. Если её там нет (или поле пустое), herdr молча откроет панель в домашней директории машины — задача выполнится не там, где вы ждали.
- Уже зарегистрированные машины нужно один раз «Перепроверить» — старая запись
в
machines.jsonне знает про herdr-провайдеров.
Дальше A — главная машина: на ней PromptPilot (worker, Web UI, бот) и общая очередь; B — вторая машина, на которую уезжают задачи. На B не нужны ни Python, ни PromptPilot — только herdr и сам агент.
1. На B поставить herdr и агента. Тем же способом, что и на A; обновление —
herdr update. Версии herdr на A и B лучше держать одинаковыми: подключение
herdr --remote требует совместимого протокола.
2. Проверить PATH login-шелла на B. PromptPilot ходит на машину как
ssh B bash -lc '...', то есть видит PATH из ~/.profile/~/.bash_profile, а
не из ~/.bashrc (он читается только интерактивными шеллами). С машины A:
ssh B "bash -lc 'command -v herdr; command -v claude'"Обе строки должны напечататься. Если пусто — на B в ~/.profile:
export PATH="$HOME/.local/bin:$PATH" # herdr, claude
export PATH="$HOME/.opencode/bin:$PATH" # если нужен opencode3. Настроить ssh по ключу (с A на B).
ssh-keygen -t ed25519 # если ключа ещё нет
ssh-copy-id user@B
ssh -o BatchMode=yes user@B true && echo OKBatchMode обязателен: PromptPilot никогда не отвечает на запрос пароля. Если
ключ с парольной фразой — держите ssh-agent и следите, чтобы воркер видел
SSH_AUTH_SOCK.
4. Зарегистрировать машину. Web UI: ⚙ Providers → 🖥 Машины → имя vm2
user@B→ «Добавить и проверить». Или через API:
curl -s -X POST localhost:8420/api/machines \
-H 'Content-Type: application/json' -d '{"name":"vm2","host":"user@B"}'В ответе — список найденных провайдеров; там должны быть claude-herdr и
herdr-session. Если их нет — вернитесь к шагу 2 (herdr или агент не видны
login-шеллу) и нажмите «Перепроверить» (POST /api/machines/vm2/probe).
5. Поставить задачу. В форме задачи: Машина — vm2, провайдер —
claude-herdr, рабочая директория — путь, который существует на B. В боте
это шаг «Где выполнить задачу?». Дальше PromptPilot сам поднимет herdr server на
B (если не запущен), создаст вкладку pp-t<id>-*, стартует агента и отправит
промпт; упёрся в permission-диалог — задача останется running, а в Telegram
придёт уведомление с кнопками.
6. Подключиться к живой сессии на B. С машины A — herdr --remote user@B,
на самой B — просто herdr. Эта же команда приходит в уведомлении и в
мета-строке результата. Оставленные задачами сессии называются pp-kept-<id>;
отправить в такую сессию следующий промпт можно провайдером herdr-session
(выпадашка показывает сессии выбранной машины).
herdr есть и под Windows — нативный herdr.exe из preview-канала:
powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex"WSL не нужен. PromptPilot определяет, на каком языке разговаривать с машиной,
сам — при добавлении/перепроверке машины и хранит это в machines.json полем
"shell": "posix" | "powershell" (в списке машин видно значком 🐧/🪟):
- posix — команда уходит как
ssh host bash -lc '<...>'; - powershell — как
ssh host powershell -EncodedCommand <base64/UTF-16LE>. Кодирование не украшательство: виндовый sshd сначала отдаёт командную строкуcmd.exe, и никакие кавычки через него живыми не проходят. В base64 нет символов, которые cmd трактует по-своему, поэтому скрипт доезжает байт в байт. Перед скриптом выставляется[Console]::OutputEncoding = UTF8— иначе JSON herdr'а и любая кириллица возвращаются кракозябрами.
Что нужно на Windows-машине:
herdr.exeи агент (claude) — в PATH пользователя, под которым ходит ssh (проверка:ssh winbox powershell -NoProfile -c "Get-Command herdr, claude").- OpenSSH Server (Параметры → Приложения → Дополнительные компоненты) с
входом по ключу; публичный ключ для админской учётки кладётся не в
~/.ssh/authorized_keys, а вC:\ProgramData\ssh\administrators_authorized_keys. - Шелл по умолчанию менять не надо — PowerShell вызывается явно; работает и
дефолтный
cmd.exe. - Порт, отличный от 22, задавайте алиасом в
~/.ssh/configна машине A — в поле «хост» можно писать имя алиаса.
Дальше всё как обычно: «Добавить и проверить» → в списке claude-herdr,
herdr-session → задача с этой машиной → подключение herdr --remote winbox.
Рабочая директория задачи — в виндовой записи (C:\work\proj).
Если на Windows-машине настроен Git Bash как shell для sshd, проба определит её
как posix — это не ошибка: такой машиной можно рулить обычным bash -lc.
Если что-то не так
| Симптом | Причина / что сделать |
|---|---|
| В списке машины нет herdr-провайдеров | ssh B "bash -lc 'command -v herdr'" пуст → PATH в ~/.profile (шаг 2), затем «Перепроверить» |
| «herdr server на машине … не запущен и не удалось его поднять» | зайдите на B и запустите herdr server руками; проверьте ssh B "bash -lc 'herdr status server --json'" |
| ssh спрашивает пароль | ключи не разложены или agent недоступен (шаг 3); на Windows-машине для админской учётки ключ кладётся в administrators_authorized_keys |
| Windows-машина определилась как posix и падает | на ней sshd отдаёт bash (Git Bash/WSL), но herdr стоит нативный — либо уберите bash из DefaultShell и перепроверьте машину, либо держите herdr там же, где bash |
| Задача отработала «не в том каталоге» | указанной рабочей директории нет на B — herdr молча ушёл в $HOME |
Задача висит в running |
агент в blocked ждёт человека: подключитесь (herdr --remote B) или подтвердите кнопкой в Telegram |
Бот (если запущен) наблюдает за всеми агентами herdr — не только за
задачами PromptPilot — на этой машине и на каждой зарегистрированной машине
с herdr-провайдерами — и присылает уведомление, когда агент заблокирован
диалогом или закончил работу незамеченным. В тексте уведомления видно, на
какой машине агент. Кнопки под уведомлением:
✅ Подтвердить (Enter) · 📺 Экран · ✍️ Ответить — работают и для
удалённых панелей, так что разблокировать агента на любой машине можно прямо
с телефона. Отключение: PP_HERDR_WATCH=0.
npm install -g @nothumanwork/cursor-agents-sdk
winget install BurntSushi.ripgrep.MSVCДобавь в .env:
CURSOR_API_KEY=crsr_your_key_here
Ключ: cursor.com/settings → API Keys. Первый запуск занимает ~60 секунд.
Скилы — команды (/skill-name) из ~/.claude/commands/, ~/.claude/skills/ и плагинов Claude Code. Доступны для всех провайдеров с supports_skills=True.
При выборе Claude-провайдера под полем промпта появляется кнопка ⚡ Skills. Нажми — откроется список скилов с описаниями. Выбор подставляет /skill-name в промпт.
Кнопка ⚡ Скилы в главном меню или команда /skills. Поддерживает глобальные скилы и скилы конкретного проекта (📁 Скилы проекта...).
GET /api/skills — все доступные скилы
GET /api/skills?provider=claude — только если провайдер поддерживает скилы
GET /api/skills?provider=claude&workdir=/path — + локальные скилы проекта
Минималистичный dark-theme UI на http://127.0.0.1:8420:
- Выбор провайдера и модели (дропдаун модели появляется автоматически для Claude Code провайдеров)
- Добавление задач с приоритетом и расписанием (Ctrl+Enter для отправки)
- Чекбокс
--dangerously-skip-permissions - ⚡ Skills — раскрывает список доступных скилов
- Фильтры по статусу, раскрытие деталей задачи
- Отмена (в т.ч. running-задач — процесс будет убит) и удаление задач
- Recur, per-task таймаут, галочка «🖥 оставить сессию открытой» (herdr)
- Кнопка ⏸ Pause воркера и панель стоимости по провайдерам
- ⚙ Providers — управление провайдерами: список с пометками (встроенный/кастомный, «не установлен», «скрыт»), добавление (команда-шаблон или herdr-сессия: агент, доп. аргументы, модели, env), «Изменить», «Скрыть/Показать», «Удалить» / «Сбросить к встроенному»
- В выпадашке провайдеров у формы задачи — только установленные и не скрытые
- Автообновление каждые 5 секунд
Доступ с другой машины — через SSH-туннель:
ssh -L 8420:127.0.0.1:8420 user@server # затем открой http://localhost:8420По умолчанию API открыт только на 127.0.0.1 и без авторизации. Если сервер
нужно открыть наружу (PP_HOST=0.0.0.0), задайте токен:
PP_API_TOKEN=длинный-случайный-токен
Браузер покажет стандартное окно логина (имя любое, пароль — токен); скрипты
ходят с заголовком Authorization: Bearer <токен> или curl -u x:<токен>.
Без токена запросы получают 401.
GET /api/tasks — список задач (?status=pending&limit=50)
POST /api/tasks — создать задачу (все поля TaskCreate, вкл. keep_pane, worktree)
GET /api/tasks/{id} — детали задачи
PATCH /api/tasks/{id} — отменить (для running — worker убьёт процесс) / сменить приоритет
DELETE /api/tasks/{id} — удалить
POST /api/tasks/{id}/reset — сбросить зависшую задачу в pending
GET /api/stats — статистика по статусам
GET /api/stats/costs — стоимость: today / week / total / by_provider
GET /api/stats/usage — расход за окно лимита по всем сессиям (?hours=5)
POST /api/tasks/{id}/note — дописать решателю ({"text": "..."}; пустой текст убирает)
GET /api/worker/status — paused + state/heartbeat_at/age_seconds/pid
POST /api/worker/pause|resume — пауза/возобновление воркера
GET /api/version — проверка обновлений (кэш 24 ч)
GET /api/providers — провайдеры (description, supports_skills, models, available, hidden, executor)
GET /api/providers/manage — полная информация для настроек (env-секреты маскированы)
POST /api/providers — создать/изменить провайдера
DELETE /api/providers/{name} — удалить кастомного провайдера
POST /api/providers/{name}/hide|unhide — скрыть/показать в списках
GET /api/skills — скилы (?provider=claude&workdir=/path)
GET /api/projects — проекты из PP_PROJECTS_ROOT ({name, path, git})
POST /api/workflows — создать draft workflow
POST /api/workflows/validate-setup — read-only preflight мастера создания
GET /api/workflows — список workflow (?status=draft&limit=50)
GET /api/workflows/{id} — детали workflow
PATCH /api/workflows/{id} — обновить draft-метаданные с expected_version
POST /api/workflows/{id}/start — создать первый новый раунд
POST /api/workflows/{id}/dispatch — поставить executor/reviewer task в очередь
POST /api/workflows/{id}/gate — записать результат ручного W1-гейта
POST /api/workflows/{id}/review — записать структурированный verdict аудитора
POST /api/workflows/{id}/human-input — решение человека/возобновление
POST /api/workflows/{id}/cancel — отменить workflow и связанные задачи
POST /api/workflows/{id}/sync — восстановить проекцию из состояния tasks
POST /api/workflows/{id}/history/import — импортировать старые раунды и факты
GET /api/workflows/{id}/rounds — раунды
GET /api/workflows/{id}/rounds/{round_id}/runs — запуски ролей
GET /api/workflows/{id}/events — append-only история (?after_seq=0)
GET /api/workflows/{id}/findings — текущие findings
GET /api/workflows/{id}/artifacts — зарегистрированные артефакты
| Переменная | По умолчанию | Описание |
|---|---|---|
PP_DATA_DIR |
~/.promptpilot |
Директория для БД |
PP_POLL_INTERVAL |
5 |
Интервал опроса очереди (сек) |
PP_TASK_TIMEOUT |
0 (без лимита) |
Глобальный таймаут задачи, сек; у задачи переопределяется индивидуально |
PP_BASE_DELAY |
60 |
Начальная задержка retry (сек) |
PP_MAX_DELAY |
3600 |
Максимальная задержка retry (сек) |
PP_MAX_RETRIES |
5 |
Макс. кол-во retry по умолчанию |
PP_CONCURRENCY |
1 |
Сколько задач worker выполняет одновременно |
PP_PIPELINE_SNAPSHOT_INTERVAL |
300 |
Интервал фоновых снимков активных pipeline-профилей, сек; 0 отключает |
PP_MIN_FREE_MB |
0 (без проверки) |
Не начинать новую задачу, если свободно меньше памяти |
PP_VERDICT |
0 |
Просить агента заканчивать строкой ИТОГ: ... (дописывается к промпту) |
PP_GUARD |
auto |
Сторож запретов: auto — при skip_permissions, 1 — всегда, 0 — выключен |
PP_WORKTREE_PREFIX |
pp/ |
Префикс ветки задачи с worktree (pp/t42) |
PP_WORKTREES_ROOT |
— | Куда класть чекауты; пусто = .pp-worktrees рядом с репозиторием |
PP_WORKTREE_COPY |
.env |
Игнорируемые git'ом файлы, которые копировать в новый чекаут (через запятую; пусто — не копировать) |
PP_DEFAULT_CLI |
claude |
Провайдер по умолчанию |
PP_HOST |
127.0.0.1 |
Хост веб-сервера |
PP_PORT |
8420 |
Порт веб-сервера |
PP_API_TOKEN |
— | Токен авторизации Web UI/API (пусто = без авторизации) |
PP_TG_TOKEN |
— | Токен Telegram бота |
PP_TG_ALLOWED_PHONES |
— | Разрешённые номера (через запятую) |
PP_TASK_PASSWORD |
— | Пароль для создания задач через бота |
PP_PROJECTS_ROOT |
— | Корневая папка проектов для быстрого выбора директории |
PP_CLAUDE_EXE |
из PATH | Путь к claude / claude.exe |
PP_HERDR_BIN |
herdr |
Путь к herdr CLI |
PP_HERDR_KEEP_PANE |
0 |
Форсировать «оставить сессию» независимо от галочки задачи |
PP_HERDR_READ_LINES |
300 |
Сколько строк транскрипта читать как результат |
PP_HERDR_START_TIMEOUT_MS |
60000 |
Таймаут готовности агента при старте сессии |
PP_HERDR_WATCH |
1 |
herdr→Telegram мост (уведомления о blocked/done) |
PP_HERDR_WATCH_INTERVAL |
10 |
Интервал опроса агентов herdr (сек) |
PP_HERDR_RENOTIFY_COOLDOWN |
600 |
Антидубль blocked-уведомлений: повтор с тем же экраном в течение этого срока молчит (сек) |
| Статус | Описание |
|---|---|
pending |
В очереди |
running |
Выполняется |
completed |
Успешно завершена |
failed |
Завершена с ошибкой |
rate_limited |
Ожидает retry: rate limit или срыв по вине среды (см. ниже) |
cancelled |
Отменена |
promptpilot/
├── config.py — настройки, провайдеры, скилы, build_cmd
├── models.py — Pydantic-модели
├── db.py — SQLite (очередь, CRUD, планирование)
├── worker.py — воркер (subprocess → любой AI CLI)
├── cli.py — CLI (Click)
├── api.py — REST API (FastAPI)
├── bot.py — Telegram бот (python-telegram-bot)
├── tg_auth.py — авторизация по номеру телефона
└── static/
└── index.html — веб-интерфейс
start.ps1 — запустить все сервисы
stop.ps1 — остановить все сервисы
build.ps1 — собрать dist\pp.exe
pp.spec — конфиг PyInstaller
Воркер и сервер — два отдельных процесса, работающих с одной SQLite БД. По
умолчанию воркер выполняет задачи по одной; PP_CONCURRENCY>1 включает
параллельное выполнение (см. раздел «Параллельные задачи»).
MIT — см. LICENSE.



