Туториал по Kanban
Разбираем четыре сценария, ради которых задумывалась система Kanban в Hermes, держа дашборд открытым в браузере. Если вы ещё не читали обзор Kanban — начните с него: здесь предполагается, что вы уже знаете, что такое задача, прогон (run), исполнитель и диспетчер.
Подготовка
hermes kanban init # optional; first `hermes kanban <anything>` auto-inits
hermes dashboard # opens http://127.0.0.1:9119 in your browser
# click Kanban in the left nav
Дашборд — самое удобное место, чтобы вам наблюдать за системой. Агенты-воркеры, которых порождает диспетчер, не видят ни дашборда, ни CLI: они управляют доской через специальный набор инструментов kanban_* (kanban_show, kanban_list, kanban_complete, kanban_request_review, kanban_request_changes, kanban_block, kanban_heartbeat, kanban_comment, kanban_attach, kanban_attach_url, kanban_attachments, kanban_create, kanban_link, kanban_unblock). Все три точки входа — дашборд, CLI, инструменты воркеров — ходят в одну и ту же SQLite-базу для каждой доски (~/.hermes/kanban.db для доски по умолчанию, ~/.hermes/kanban/boards/<slug>/kanban.db для любой доски, которую вы создадите позже). Поэтому состояние доски остаётся согласованным, с какой бы стороны изменение ни пришло.
Весь туториал работает с доской default. Если вам нужно несколько изолированных очередей (по одной на проект / репозиторий / домен), загляните в раздел Доски (мультипроект) обзора — там те же потоки CLI / дашборда / воркеров применяются к каждой доске отдельно, а воркеры физически не способны видеть задачи на чужих досках.
По всему туториалу действует простое правило: блоки кода с меткой bash — это команды, которые запускаете вы. Блоки с меткой # worker tool calls — это вызовы инструментов, которые выдаёт модель порождённого воркера. Они приведены, чтобы вы видели цикл агента целиком, а не для того, чтобы вы их сами набирали.
Доска с высоты птичьего полёта

Шесть колонок слева направо:
- Triage — сырые идеи. По умолчанию диспетчер автоматически запускает на здешних задачах декомпозер: встроенный декомпозер использует
auxiliary.kanban_decomposer, читает ваш набор профилей и описания и строит граф дочерних задач, разведённых по наиболее подходящим специалистам. Исходная задача остаётся живой как родитель, чтобы её исполнитель (kanban.orchestrator_profile, либо активный профиль по умолчанию, если параметр не задан) снова проснулся и оценил завершение, когда всё доделается. Чтобы переключить режим, щёлкните по пилюле Orchestration: Auto/Manual в верхней части страницы kanban. В ручном режиме нажмите ⚗ Decompose на карточке либо выполнитеhermes kanban decompose <id>//kanban decompose <id>. Для одиночных задач, которым fan-out не нужен, кнопка ✨ Specify делает однократную переписку спецификации (цель, подход, критерии приёмки) и продвигает задачу вtodo. Модели для этого настраиваются черезauxiliary.kanban_decomposerиauxiliary.triage_specifierвconfig.yaml. См. Авто- против ручной оркестрации в основном руководстве по Kanban. - Todo — задача создана, но ждёт зависимостей или ещё не назначена.
- Ready — назначена и ждёт, пока диспетчер её заберёт.
- In progress — воркер активно выполняет задачу. С включёнными «Lanes by profile» (по умолчанию) колонка группируется по исполнителю, и вы с одного взгляда видите, кто чем занят.
- Blocked — воркер запросил вмешательство человека либо сработал предохранитель (circuit breaker).
- Done — выполнено.
В верхней панели — фильтры по поиску, тенанту и исполнителю, плюс переключатель Lanes by profile и кнопка Nudge dispatcher, которая прокручивает один такт диспетчера прямо сейчас, не дожидаясь следующего интервала демона. Клик по любой карточке открывает её ящик (drawer) справа.
Плоский вид
Если дорожки профилей рябят в глазах, отключите «Lanes by profile» — и колонка In Progress схлопнется в единый плоский список, упорядоченный по времени захвата:

История 1 — Один разработчик выкатывает фичу
Вы пилите фичу. Классический поток: спроектировать схему, реализовать API, написать тесты. Три задачи с зависимостями родитель→потомок.
SCHEMA=$(hermes kanban create "Design auth schema" \
--assignee backend-dev --tenant auth-project --priority 2 \
--body "Design the user/session/token schema for the auth module." \
--json | jq -r .id)
API=$(hermes kanban create "Implement auth API endpoints" \
--assignee backend-dev --tenant auth-project --priority 2 \
--parent $SCHEMA \
--body "POST /register, POST /login, POST /refresh, POST /logout." \
--json | jq -r .id)
hermes kanban create "Write auth integration tests" \
--assignee qa-dev --tenant auth-project --priority 2 \
--parent $API \
--body "Cover happy path, wrong password, expired token, concurrent refresh."
Поскольку родитель API — это SCHEMA, а родитель tests — это API, в ready стартует только SCHEMA. Остальные две лежат в todo, пока их родители не завершатся. Так работает движок продвижения зависимостей — ни один воркер не возьмётся за написание тестов, пока нет API, который можно тестировать.
На следующем такте диспетчера (по умолчанию 60 секунд, либо сразу, если нажать Nudge dispatcher) профиль backend-dev порождается как воркер с HERMES_KANBAN_TASK=$SCHEMA в окружении. Вот как выглядит цикл вызовов инструментов изнутри агента:
# worker tool calls — NOT commands you run
kanban_show()
# → возвращает title, body, worker_context, parents, прошлые попытки, комментарии
# (воркер читает worker_context, проектирует схему через терминал/файловые
# инструменты, пишет миграции, гоняет собственные проверки, коммитит —
# реальная работа происходит здесь)
kanban_heartbeat(note="schema drafted, writing migrations now")
kanban_complete(
summary="users(id, email, pw_hash), sessions(id, user_id, jti, expires_at); "
"refresh tokens stored as sessions with type='refresh'",
metadata={
"changed_files": ["migrations/001_users.sql", "migrations/002_sessions.sql"],
"decisions": ["bcrypt for hashing", "JWT for session tokens",
"7-day refresh, 15-min access"],
},
)
У kanban_show параметр task_id по умолчанию равен $HERMES_KANBAN_TASK, так что воркеру не нужно знать собственный id. kanban_complete записывает summary и metadata в текущую строку task_runs, закрывает этот прогон и переводит задачу в done — всё одним атомарным шагом через kanban_db.
Как только SCHEMA доходит до done, движок зависимостей автоматически продвигает API в ready. Воркер API, когда возьмётся за дело, вызовет kanban_show() и увидит summary и metadata от SCHEMA, приложенные к родительской передаче. Так он узнаёт решения по схеме, не перечитывая длинный проектный документ.
Кликните по завершённой задаче со схемой — и ящик покажет всё:

Главное добавление — секция Run History внизу. Одна попытка: исход completed, воркер @backend-dev, длительность, метка времени и полный текст summary передачи. Блок metadata (changed_files, decisions) тоже хранится на прогоне и попадает к любому нижестоящему воркеру, который читает этого родителя.
Те же данные можно посмотреть из терминала в любой момент — эти команды вы подглядываете за доской, а не воркер:
hermes kanban show $SCHEMA
hermes kanban runs $SCHEMA
# # OUTCOME PROFILE ELAPSED STARTED
# 1 completed backend-dev 0s 2026-04-27 19:34
# → users(id, email, pw_hash), sessions(id, user_id, jti, expires_at); refresh tokens ...
История 2 — Конвейер из множества воркеров
У вас три воркера (переводчик, транскрибатор, копирайтер) и куча независимых задач. Хочется, чтобы все трое тянули работу параллельно и давали видимый прогресс. Это простейший сценарий kanban — и тот, под который исходно затачивался дизайн.
Создаём работу:
for lang in Spanish French German; do
hermes kanban create "Translate homepage to $lang" \
--assignee translator --tenant content-ops
done
for i in 1 2 3 4 5; do
hermes kanban create "Transcribe Q3 customer call #$i" \
--assignee transcriber --tenant content-ops
done
for sku in 1001 1002 1003 1004; do
hermes kanban create "Generate product description: SKU-$sku" \
--assignee copywriter --tenant content-ops
done
Запустите шлюз и идите заниматься своими делами — он держит встроенный диспетчер, который разбирает задачи всех трёх профилей-специалистов на одной и той же kanban.db:
hermes gateway start
Теперь отфильтруйте доску по content-ops (или просто поищите «Transcribe») — и получите вот что:

Две транскрибации готовы, одна выполняется, две в ready ждут следующего такта диспетчера. Колонка In Progress сгруппирована по профилю (поведение «Lanes by profile» по умолчанию), так что активная задача каждого воркера видна без вычитывания смешанного списка. Диспетчер продвинет следующую готовую задачу в работу, как только завершится текущая. Три демона трудятся над тремя пулами исполнителей параллельно — и вся очередь контента вычерпывается без дальнейшего участия человека.
Всё, что в Истории 1 говорилось про структурированную передачу, действует и здесь. Воркер-переводчик, завершая звонок, выдаёт kanban_complete(summary="translated 4 pages, style matched existing marketing voice", metadata={"duration_seconds": 720, "tokens_used": 2100}) — это полезно и для аналитики, и для любой нижестоящей задачи, которая от этой зависит.
История 3 — Ролевой конвейер с повторной попыткой
Вот где Kanban оправдывает себя по сравнению с плоским списком TODO. PM пишет спецификацию. Инженер её реализует. Ревьюер отклоняет первую попытку. Инженер пробует снова, с правками. Ревьюер одобряет.
Вид дашборда с фильтром по auth-project:

На скриншоте — модель заранее созданной дочерней карточки: у карточки реализации есть выделенный дочерний ревью. В этой модели инженер обязан вызвать kanban_complete, как только реализация готова, — тогда дочерняя ревью-карточка сможет выйти из todo. Не блокируйте родительскую карточку реализации только ради того, чтобы запросить ревью.
Для потоков, где одна и та же карточка отвечает и за реализацию, и за ревью, используйте вместо этого встроенный жизненный цикл ревью. Полная хореография implement → review → changes → re-review выглядит так:
# --- Инженер: первая попытка реализации ---
kanban_show()
# (пишем код, гоняем тесты, готовим кандидата)
kanban_request_review(
summary="implemented reset flow; candidate is ready for review",
metadata={"changed_files": ["auth/reset.py"], "tests_run": 8},
reviewer="reviewer",
)
# → та же карточка переходит в ревью; прогон реализации закрывается с
# исходом 'review_requested'
# --- Ревьюер: запрашивает конкретные правки ---
kanban_show()
# (изучаем передачу и кандидата)
kanban_request_changes(
reason="Add password-strength validation and make reset tokens single-use."
)
# → прогон ревью закрывается с исходом 'changes_requested'; карточка
# возвращается к backend-dev в ready/todo, не трогая счётчик блокировок
# --- Инженер: вторая попытка реализации ---
kanban_show() # предыдущий фидбэк ревью уже в worker_context
# (применяем замечания и перезапускаем тесты)
kanban_request_review(
summary="added zxcvbn validation and single-use reset tokens",
metadata={
"changed_files": [
"auth/reset.py",
"auth/tests/test_reset.py",
"migrations/003_single_use_reset_tokens.sql",
],
"tests_run": 11,
"review_iteration": 2,
},
reviewer="reviewer",
)
# --- Ревьюер: одобряет ---
kanban_complete(summary="review passed; acceptance criteria verified")
# → done
История прогонов задачи теперь фиксирует review_requested → changes_requested → review_requested → completed. У каждой попытки свой актор, summary, metadata и исход, так что второй инженер видит ровно то, что отклонил ревьюер, а финальное одобрение остаётся прослеживаемым. kanban_block предназначен для настоящей внешней эскалации (нет доступа, нужно продуктовое решение, недоступна инфраструктура), а не для обычного фидбэка по ревью.
Если вы намеренно используете модель с отдельной дочерней карточкой, показанную на скриншоте, ревьюер открывает Review password reset PR после того, как завершится её родитель-реализация:

worker_context карточки ревьюера включает передачу завершённой реализации. Это отдельный сценарий работы с карточками — не смешивайте его с kanban_request_review внутри одной карточки, иначе получите дублирующую дорожку ревью.
История 4 — Предохранитель и восстановление после краха
Реальные воркеры падают. Отсутствующие учётные данные, OOM-киллы, разовые сетевые сбои. У диспетчера две линии обороны: предохранитель (circuit breaker), который автоблокирует задачу после N подряд идущих сбоев, чтобы доска не молотила вхолостую вечно, и детектор краха, который возвращает задачу, чей воркер пропал по PID раньше истечения её TTL.
Предохранитель — сбой, похожий на постоянный
Задача деплоя, которая не может породить воркер, потому что в окружении профиля не задан AWS_ACCESS_KEY_ID:
hermes kanban create "Deploy to staging (missing creds)" \
--assignee deploy-bot --tenant ops \
--max-retries 3
Диспетчер пытается породить воркер. Порождение падает (RuntimeError: AWS_ACCESS_KEY_ID not set). Диспетчер снимает захват, увеличивает счётчик сбоев и пробует снова на следующем такте. Поскольку в этом примере задан --max-retries 3, предохранитель срабатывает после трёх подряд идущих сбоев: задача уходит в blocked с исходом gave_up. Если опустить флаг, Hermes использует kanban.failure_limit (по умолчанию: 2). Дальнейших повторов не будет, пока человек не снимет блокировку.
Кликните по заблокированной задаче:

Три прогона, у всех одинаковая ошибка в поле error. Первые два — spawn_failed (повторяемые), третий — gave_up (терминальный). Журнал событий выше показывает всю последовательность: created → claimed → spawn_failed → claimed → spawn_failed → claimed → gave_up.
В терминале:
hermes kanban runs t_ef5d
# # OUTCOME PROFILE ELAPSED STARTED
# 1 spawn_failed deploy-bot 0s 2026-04-27 19:34
# ! AWS_ACCESS_KEY_ID not set in deploy-bot env
# 2 spawn_failed deploy-bot 0s 2026-04-27 19:34
# ! AWS_ACCESS_KEY_ID not set in deploy-bot env
# 3 gave_up deploy-bot 0s 2026-04-27 19:34
# ! AWS_ACCESS_KEY_ID not set in deploy-bot env
Если подключён Telegram / Discord / Slack, на событие gave_up срабатывает уведомление шлюза — и вы узнаёте об аварии, не заглядывая в доску.
Восстановление после краха — воркер умирает на лету
Иногда порождение проходит успешно, но процесс воркера умирает позже — segfault, OOM, systemctl stop. Диспетчер опрашивает kill(pid, 0), обнаруживает мёртвый pid; захват снимается, задача возвращается в ready, и следующий такт отдаёт её свежему воркеру.
Пример в начальных данных (seed data) — миграция, у которой кончалась память:
# Worker claims, starts scanning 2.4M rows, OOM kills it at ~2.3M
# Dispatcher detects dead pid, releases claim, increments attempt counter
# Retry with a chunked strategy succeeds
Ящик показывает полную историю из двух попыток:

Прогон 1 — crashed, с ошибкой OOM kill at row 2.3M (process 99999 gone). Прогон 2 — completed, с "strategy": "chunked with LIMIT + WHERE id > last_id" в metadata. Воркер-повторщик увидел в своём контексте крах прогона 1 и выбрал более безопасную стратегию; благодаря metadata будущему наблюдателю (или автору постмортема) очевидно, что именно изменилось.
Структурированная передача — зачем нужны summary и metadata
В каждой истории выше воркеры в конце вызывали kanban_complete(summary=..., metadata=...). Это не украшение, а основной канал передачи между этапами рабочего процесса.
Когда воркер на задаче B порождается и вызывает kanban_show(), в полученном worker_context содержится:
- Прошлые попытки задачи B (предыдущие прогоны: исход, summary, error, metadata), чтобы воркер-повторщик не пошёл по уже провалившемуся пути.
- Результаты родительских задач — для каждого родителя summary и metadata самого свежего завершённого прогона — чтобы нижестоящие воркеры видели, зачем и как делалась вышестоящая работа.
Это снимает танец «копайся в комментариях и выводе работы», который мучает плоские kanban-системы. PM пишет критерии приёмки в metadata спецификации — и воркер инженера видит их структурно в родительской передаче. Инженер фиксирует, какие тесты он гонял и сколько прошло, — и у воркера ревьюера этот список под рукой ещё до открытия дифа.
Защита от массового закрытия существует именно потому, что эти данные привязаны к прогону. hermes kanban complete a b c --summary X (вы, из CLI) отклоняется — копипастить одно и то же summary в три задачи почти всегда неправильно. Массовое закрытие без флагов передачи по-прежнему работает для типичного случая «я доделал кучу административных мелочей». В наборе инструментов массового варианта нет вовсе: kanban_complete всегда работает с одной задачей за раз по той же причине.
Донастройка завершённой карточки — исправление CI через родительскую ссылку
Карточка реализации из Истории 1 в статусе done. Через два часа на смёрженной ветке падает CI. Не открывайте заново завершённую карточку — завершённые карточки это история, а их передача идёт вперёд. Создайте карточку-исправление, указав завершённую карточку её родителем:
hermes kanban create "Fix CI: test_backoff_jitter flakes on 3.11" \
--assignee backend-dev \
--parent t_impl \
--workspace worktree --branch wt/ci-fix-backoff \
--body "CI run #4812 failed after t_impl completed.
FAILED tests/test_retry.py::test_backoff_jitter - TimeoutError
Acceptance: tests/test_retry.py green on 3.11 and 3.12."
Это работает благодаря трём вещам:
- Немедленный запуск. Раз родитель уже
done, дочерняя задача создаётся сразу вready— диспетчер может забрать её на следующем такте. (Дочерняя задача ещё не закрытого родителя ждала бы вtodo.) - Унаследованный контекст. В контексте воркера-исправителя есть секция Parent task results с summary и metadata завершения
t_impl— изменённые файлы и решения, которые записал исходный воркер. Так он понимает, почему код устроен именно так, ещё до чтения первой строки. - Свежие улики в теле задачи. Лога CI не существовало, когда
t_implзавершалась, поэтому его не может быть в передаче родителя — он идёт в тело новой карточки вместе с явными критериями приёмки.
Для карточки-исправления предпочтителен свежий worktree/ветка. Переключение на исходную ветку даёт воркеру состояние репозитория, но не даёт обоснования — его несёт передача от родителя. Обычно правильно назначить тот же профиль: профиль, который написал код, обладает навыками, чтобы его исправить.
Осмотр задачи, которая выполняется прямо сейчас
Для полноты картины — вот ящик задачи в полёте (реализация API из Истории 1, захваченная backend-dev, но ещё не завершённая):

Статус — Running. Активный прогон отображается в секции Run History с исходом active и без ended_at. Если этот воркер умрёт или выйдет за таймаут, диспетчер закроет прогон с подходящим исходом и откроет новый при следующем захвате — строка попытки никогда не исчезает.
Дальнейшие шаги
- Обзор Kanban — полная модель данных, словарь событий и справочник по CLI.
hermes kanban --help— каждая подкоманда, каждый флаг.hermes kanban watch --kinds completed,gave_up,timed_out— живой поток терминальных событий по всей доске в терминале.hermes kanban notify-subscribe <task> --platform telegram --chat-id <id>— получить пинг от шлюза, когда конкретная задача завершится.