Перейти к содержанию

Kanban

На этой странице

Хотите пошаговое руководство? Прочитайте Учебник по Kanban — четыре пользовательские истории (одиночный разработчик, управление фермой, пайплайн ролей с повторами и автоматический выключатель) со скриншотами панели управления для каждой. Эта страница — справочная; учебник — повествовательный.

Hermes Kanban — это надёжная доска задач, общая для всех ваших профилей Hermes, которая позволяет нескольким именованным агентам совместно работать без хрупких внутрипроцессных роёв подагентов. Каждая задача — это строка в ~/.hermes/kanban.db; каждая передача — это строка, которую любой может прочитать и записать; каждый рабочий — это полноценный OS-процесс с собственной идентичностью.

Два интерфейса: модель общается через инструменты, вы — через CLI​

У доски есть два входа, оба работают с одной и той же ~/.hermes/kanban.db:

  • Агенты управляют доской через специальный набор инструментов kanban_* — kanban_show, kanban_complete, kanban_block, kanban_heartbeat, kanban_comment, kanban_create, kanban_link. Диспетчер порождает каждого рабочего с уже включёнными в его схему этими инструментами; модель читает свою задачу и передаёт работу, вызывая их напрямую, а не через вызов hermes kanban в оболочке. См. раздел Как рабочие взаимодействуют с доской ниже.
  • Вы (и скрипты, и cron) управляете доской через hermes kanban … в CLI, через /kanban … как слэш-команду или через панель управления. Они предназначены для людей и автоматизации — для мест, за которыми не стоит модель, вызывающая инструменты.

Оба интерфейса проходят через один и тот же слой kanban_db, поэтому чтение всегда показывает согласованное представление, а записи не расходятся. В остальной части этой страницы приведены примеры для CLI, потому что их легко копировать, но у каждой CLI-команды есть эквивалентный вызов инструмента, который использует модель.

Это форма, которая охватывает рабочие нагрузки, с которыми delegate_task не справляется:

  • Триаж исследований — параллельные исследователи + аналитик + писатель, человек в цикле.
  • Плановые операции — повторяющиеся ежедневные брифинги, которые накапливают журнал за недели.
  • Цифровые двойники — постоянные именованные ассистенты (inbox-triage, ops-review), накапливающие память со временем.
  • Инженерные пайплайны — декомпозиция → реализация в параллельных рабочих деревьях → ревью → итерация → PR.
  • Фермерская работа — один специалист, управляющий N субъектами (50 соцсетей, 12 отслеживаемых сервисов).

Полное обоснование дизайна, сравнительный анализ с Cline Kanban / Paperclip / NanoClaw / Google Gemini Enterprise и восемь канонических шаблонов совместной работы см. в docs/hermes-kanban-v1-spec.pdf в репозитории.

Kanban vs delegate_task​

Они выглядят похоже, но это не один и тот же примитив.

delegate_task Kanban
Форма RPC-вызов (fork → join) Надёжная очередь сообщений + конечный автомат
Родитель Блокируется до возврата потомка Создал и забыл после create
Идентичность потомка Анонимный подагент Именованный профиль с постоянной памятью
Возобновляемость Нет — не удалось = не удалось Блок → разблокировать → перезапуск; сбой → возврат
Человек в цикле Не поддерживается Комментарий / разблокировка в любой момент
Агентов на задачу Один вызов = один подагент N агентов за время жизни задачи (повтор, ревью, доработка)
Аудит-след Теряется при сжатии контекста Постоянные строки в SQLite навсегда
Координация Иерархическая (вызывающий → вызываемый) Равноправная — любой профиль читает/пишет любую задачу

Одно предложение для различия: delegate_task — это вызов функции; Kanban — это очередь работы, где каждая передача — это строка, которую любой профиль (или человек) может увидеть и отредактировать.

Используйте delegate_task, когда родительскому агенту нужен короткий обдуманный ответ перед продолжением, без участия людей, результат возвращается в контекст родителя.

Используйте Kanban, когда работа пересекает границы агентов, должна пережить перезапуски, может потребовать ввода человека, может быть подхвачена другой ролью или должна быть обнаружима постфактум.

Они сосуществуют: рабочий kanban может внутренне вызывать delegate_task во время своего выполнения.

Основные понятия​

  • Доска (Board) — самостоятельная очередь задач с собственной БД SQLite, директорией рабочих областей и циклом диспетчера. Одна установка может иметь много досок (например, по одной на проект, репозиторий или домен); см. раздел Доски (мультипроект) ниже. Пользователи с одним проектом работают на доске default и никогда не видят слово «доска» за пределами этого раздела документации.
  • Задача (Task) — строка с заголовком, необязательным телом, одним исполнителем (имя профиля), статусом (triage | todo | ready | running | blocked | done | archived), необязательным пространством имён арендатора и необязательным ключом идемпотентности (дедупликация для повторяющейся автоматизации).
  • Связь (Link) — строка task_links, записывающая зависимость родитель → потомок. Диспетчер переводит todo → ready, когда все родители переходят в done.
  • Комментарий (Comment) — протокол меж-агентного взаимодействия. Агенты и люди добавляют комментарии; когда рабочий (повторно) запускается, он читает всю ветку комментариев как часть своего контекста.
  • Рабочая область (Workspace) — директория, в которой работает рабочий. Три вида:
  • scratch (по умолчанию) — свежая временная директория в ~/.hermes/kanban/workspaces/<id>/ (или ~/.hermes/kanban/boards/<slug>/workspaces/<id>/ для нестандартных досок).
  • dir:<path> — существующая общая директория (хранилище Obsidian, директория почтовых операций, папка для каждой учётной записи). Должен быть абсолютным путём. Относительные пути, такие как dir:../tenants/foo/, отклоняются при диспетчеризации, потому что они разрешались бы относительно текущей рабочей директории диспетчера, что двусмысленно и представляет собой вектор побега для запутанного посредника. В остальном путь считается доверенным — это ваша машина, ваша файловая система, рабочий работает с вашим uid. Это модель угроз доверенного локального пользователя; kanban по замыслу однопользовательский.
  • worktree — git worktree в .worktrees/<id>/ для задач программирования. Создаётся через git worktree add на стороне рабочего.
  • Диспетчер (Dispatcher) — долгоживущий цикл, который каждые N секунд (по умолчанию 60): возвращает устаревшие захваты, возвращает упавшие рабочие (PID исчез, но TTL ещё не истёк), переводит готовые задачи, атомарно захватывает, порождает назначенные профили. Работает внутри шлюза по умолчанию (kanban.dispatch_in_gateway: true). Один диспетчер обрабатывает все доски за один тик; рабочие запускаются с установленной переменной HERMES_KANBAN_BOARD, чтобы не видеть другие доски. После ~5 последовательных ошибок запуска одной и той же задачи диспетчер автоматически блокирует её с последней ошибкой в качестве причины — предотвращает бесконечные попытки для задач, профиль которых не существует, рабочая область не монтируется и т.д.
  • Арендатор (Tenant) — необязательное строковое пространство имён внутри доски. Один флот специалистов может обслуживать несколько бизнесов (--tenant business-a) с изоляцией данных через путь рабочей области и префикс ключа памяти. Арендаторы — это мягкий фильтр; доски — это жёсткая граница изоляции.

Доски (мультипроект)​

Доски позволяют разделять несвязанные потоки работы — по одному на проект, репозиторий или домен — в изолированные очереди. Новая установка имеет ровно одну доску с именем default (БД в ~/.hermes/kanban.db для обратной совместимости). Пользователи, которым нужен только один поток работы, никогда не должны знать о досках; эта функция опциональна.

Изоляция на доску абсолютна:

  • Отдельная БД SQLite на доску (~/.hermes/kanban/boards/<slug>/kanban.db).
  • Отдельные директории workspaces/ и logs/.
  • Рабочие, порождённые для задачи, видят только задачи своей доски — диспетчер устанавливает HERMES_KANBAN_BOARD в окружение потомка, и каждый инструмент kanban_*, доступный рабочему, читает её.
  • Связывание задач между досками не разрешено (чтобы схема оставалась простой; если вам действительно нужны межпроектные ссылки, используйте упоминания в свободном тексте и ищите их по id вручную).

Управление досками через CLI​

[code] # See what's on disk. Fresh installs show only "default". hermes kanban boards list

# Create a new board.
hermes kanban boards create atm10-server \
    --name "ATM10 Server" \
    --description "Minecraft modded server ops" \
    --icon 🎮 \
    --switch                   # optional: make it the active board

# Operate on a specific board without switching.
hermes kanban --board atm10-server list
hermes kanban --board atm10-server create "Restart ATM server" --assignee ops

# Change which board is "current" for subsequent calls.
hermes kanban boards switch atm10-server
hermes kanban boards show             # who's active right now?

# Rename the display name (the slug is immutable — it's the directory name).
hermes kanban boards rename atm10-server "ATM10 (Prod)"

# Archive (default) — moves the board's dir to boards/_archived/<slug>-<ts>/.
# Recoverable by moving the dir back.
hermes kanban boards rm atm10-server

# Hard delete — `rm -rf` the board dir. No recovery.
hermes kanban boards rm atm10-server --delete

[/code]

Порядок разрешения доски (наивысший приоритет первый):

  1. Явный --board <slug> при вызове CLI.
  2. Переменная окружения HERMES_KANBAN_BOARD (устанавливается диспетчером при запуске рабочего, чтобы рабочие не видели другие доски).
  3. ~/.hermes/kanban/current — slug, сохранённый командой hermes kanban boards switch.
  4. default.

Slug валидируются: строчные буквы латиницы + цифры + дефисы + подчёркивания, 1-64 символа, должны начинаться с буквы или цифры. Ввод в верхнем регистре автоматически приводится к нижнему. Всё остальное (слеши, пробелы, точки, ..) отклоняется на уровне CLI, чтобы трюки с обходом пути не могли назвать доску.

Управление досками через панель управления​

hermes dashboard → вкладка Kanban показывает переключатель досок вверху, как только появляется более одной доски (или любая доска имеет задачи). Пользователи с одной доской видят только небольшую кнопку + New board; переключатель скрыт, пока не потребуется.

  • Board dropdown — выберите активную доску. Ваш выбор сохраняется в localStorage браузера, так что он переживает перезагрузки, не сдвигая указатель current CLI из-под терминала, который вы оставили открытым.
  • + New board — открывает модальное окно с запросом slug, отображаемого имени, описания и иконки. Опция автоматического переключения на новую доску.
  • Archive — показывается только для не-default досок. Подтверждает, затем перемещает директорию доски в boards/_archived/.

Все конечные точки API панели управления принимают ?board=<slug> для привязки к доске. WebSocket событий привязывается к доске при соединении; переключение в интерфейсе открывает новый WebSocket для новой доски.

Быстрый старт​

Команды ниже — вы (человек) настраиваете доску и создаёте задачи. После назначения задачи диспетчер запускает назначенный профиль как рабочего, и оттуда модель управляет задачей через вызовы инструментов kanban_*, а не через CLI-команды — см. Как рабочие взаимодействуют с доской.

[code] # 1. Create the board (you) hermes kanban init

# 2. Start the gateway (hosts the embedded dispatcher)
hermes gateway start

# 3. Create a task (you — or an orchestrator agent via kanban_create)
hermes kanban create "research AI funding landscape" --assignee researcher

# 4. Watch activity live (you)
hermes kanban watch

# 5. See the board (you)
hermes kanban list
hermes kanban stats

[/code]

Когда диспетчер подхватывает t_abcd и запускает профиль researcher, первое, что делает модель этого рабочего — вызывает kanban_show(), чтобы прочитать свою задачу. Она не запускает hermes kanban show t_abcd.

Диспетчер, встроенный в шлюз (по умолчанию)​

Диспетчер работает внутри процесса шлюза. Ничего устанавливать не нужно, никакого отдельного сервиса для управления — если шлюз работает, готовые задачи будут подхвачены на следующем тике (60 с по умолчанию).

[code] # config.yaml kanban: dispatch_in_gateway: true # default dispatch_interval_seconds: 60 # default [/code]

Переопределите флаг конфигурации во время выполнения через HERMES_KANBAN_DISPATCH_IN_GATEWAY=0 для отладки. Применяется стандартный надзор шлюза: запустите hermes gateway start напрямую или подключите шлюз как systemd-юнит пользователя (см. документацию шлюза). Без работающего шлюза задачи со статусом ready остаются на месте, пока шлюз не появится — hermes kanban create предупреждает об этом при создании.

Запуск hermes kanban daemon как отдельного процесса устарел; используйте шлюз. Если вы действительно не можете запустить шлюз (политика хоста безголового режима запрещает долгоживущие сервисы и т.п.), запасной вариант --force сохраняет старый отдельный демон на один цикл релиза, но одновременный запуск встроенного в шлюз диспетчера И отдельного демона для одной и той же kanban.db вызывает гонки захватов и не поддерживается.

Идемпотентное создание (для автоматизации / вебхуков)​

[code] # First call creates the task. Any subsequent call with the same key # returns the existing task id instead of duplicating. hermes kanban create "nightly ops review" \ --assignee ops \ --idempotency-key "nightly-ops-$(date -u +%Y-%m-%d)" \ --json [/code]

Массовые CLI-команды​

Все команды жизненного цикла принимают несколько id, чтобы вы могли обработать партию одной командой:

[code] hermes kanban complete t_abc t_def t_hij --result "batch wrap" hermes kanban archive t_abc t_def t_hij hermes kanban unblock t_abc t_def hermes kanban block t_abc "need input" --ids t_def t_hij [/code]

Как рабочие взаимодействуют с доской​

Рабочие не запускают hermes kanban в оболочке. Когда диспетчер запускает рабочего, он устанавливает HERMES_KANBAN_TASK=t_abcd в окружение потомка, и эта переменная окружения активирует специальный набор kanban-инструментов в схеме модели — семь инструментов, которые читают и изменяют доску напрямую через Python-слой kanban_db, точно так же, как это делает CLI. Работающий рабочий вызывает их как любые другие инструменты; он никогда не видит CLI hermes kanban и не нуждается в нём.

Инструмент Назначение Обязательные параметры
kanban_show Прочитать текущую задачу (заголовок, тело, предыдущие попытки, передачи от родителя, комментарии, полный предварительно отформатированный worker_context). По умолчанию использует id задачи из окружения. —
kanban_complete Завершить со структурированной передачей summary + metadata. хотя бы один из summary / result
kanban_block Эскалировать для ввода человека с указанием reason. reason
kanban_heartbeat Сигнализировать о жизнеспособности во время долгих операций. Чистый побочный эффект. —
kanban_comment Добавить постоянную заметку в ветку задачи. task_id, body
kanban_create (Оркестраторы) разветвляться на дочерние задачи с указанием assignee, опционально parents, skills и т.д. title, assignee
kanban_link (Оркестраторы) добавить ребро зависимости parent_id → child_id постфактум. parent_id, child_id

Типичный оборот рабочего выглядит так:

[code] # Model's tool calls, in order: kanban_show() # no args — uses HERMES_KANBAN_TASK # (model reads the returned worker_context, does the work via terminal/file tools) kanban_heartbeat(note="halfway through — 4 of 8 files transformed") # (more work) kanban_complete( summary="migrated limiter.py to token-bucket; added 14 tests, all pass", metadata={"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14}, ) [/code]

Оркестратор вместо этого разветвляется:

[code] kanban_show() kanban_create( title="research ICP funding 2024-2026", assignee="researcher-a", body="focus on seed + series A, North America, AI-adjacent", ) # → returns {"task_id": "t_r1", ...} kanban_create(title="research ICP funding — EU angle", assignee="researcher-b", body="…") # → returns {"task_id": "t_r2", ...} kanban_create( title="synthesize findings into launch brief", assignee="writer", parents=["t_r1", "t_r2"], # promotes to ready when both complete body="one-pager, 300 words, neutral tone", ) kanban_complete(summary="decomposed into 2 research tasks + 1 writer; linked dependencies") [/code]

Три инструмента «(Оркестраторы)» — kanban_create, kanban_link и kanban_comment для чужих задач — доступны каждому рабочему; соглашение (подкреплённое навыком kanban-orchestrator) гласит, что профили рабочих не разветвляются, а профили оркестраторов не выполняют.

Почему инструменты вместо вызова hermes kanban в оболочке​

Три причины:

  1. Переносимость бэкенда. Рабочие, чей инструмент терминала указывает на удалённый бэкенд (Docker / Modal / Singularity / SSH), запускали бы hermes kanban complete внутри контейнера, где hermes не установлен, а ~/.hermes/kanban.db не смонтирован. Инструменты kanban работают в собственном Python-процессе агента и всегда достигают ~/.hermes/kanban.db независимо от бэкенда терминала.
  2. Нет хрупкости экранирования оболочки. Передача --metadata '{"files": [...]}' через shlex + argparse — это скрытая грабля. Структурированные аргументы инструмента полностью её обходят.
  3. Лучшие ошибки. Результаты инструментов — это структурированный JSON, с которым модель может работать, а не строки stderr, которые нужно парсить.

Нулевой след в схеме для обычных сессий. Обычная сессия hermes chat не имеет ни одного инструмента kanban_* в своей схеме. check_fn каждого инструмента возвращает True только когда установлена HERMES_KANBAN_TASK, что происходит только когда диспетчер запустил этот процесс. Никакого раздувания инструментов для пользователей, которые никогда не касаются kanban.

Навыки kanban-worker и kanban-orchestrator учат модель, какой инструмент вызывать когда и в каком порядке.

Рекомендуемые свидетельства передачи​

kanban_complete(summary=..., metadata={...}) намеренно гибок: summary — это читаемое человеком закрытие, а metadata — это машиночитаемая передача, которую downstream-агенты, рецензенты или панели управления могут использовать без сканирования прозы.

Для инженерных задач и задач ревью предпочтительна следующая опциональная форма метаданных:

[code] { "changed_files": ["path/to/file.py"], "verification": ["pytest tests/hermes_cli/test_kanban_db.py -q"], "dependencies": ["parent task id or external issue, if any"], "blocked_reason": null, "retry_notes": "what failed before, if this was a retry", "residual_risk": ["what was not tested or still needs human review"] } [/code]

Эти ключи — соглашение, а не требование схемы. Полезное свойство в том, что каждый рабочий оставляет достаточно свидетельств, чтобы следующий читатель мог быстро ответить на четыре вопроса:

  1. Что изменилось?
  2. Как это было проверено?
  3. Что может разблокировать или повторить это, если не удастся?
  4. Какой риск сознательно остаётся открытым?

Храните секреты, сырые логи, токены, OAuth-материалы и несвязанные транскрипты вне metadata. Вместо этого сохраняйте указатели и сводки. Если у задачи нет файлов или тестов, явно укажите это в summary и используйте metadata для тех свидетельств, которые существуют, такие как URL источников, id тикетов или шаги ручного ревью.

Навык рабочего​

Любой профиль, который должен иметь возможность работать с задачами kanban, должен загрузить навык kanban-worker. Он учит рабочего полному жизненному циклу в вызовах инструментов, а не в CLI-командах:

  1. При запуске вызвать kanban_show(), чтобы прочитать заголовок + тело + передачи от родителя + предыдущие попытки + полную ветку комментариев.
  2. cd $HERMES_KANBAN_WORKSPACE (через инструмент терминала) и выполнять работу там.
  3. Вызывать kanban_heartbeat(note="...") каждые несколько минут во время долгих операций.
  4. Завершить с помощью kanban_complete(summary="...", metadata={...}) или kanban_block(reason="..."), если застряли.

Загрузите его (это вы, устанавливаете в профиль — не вызов инструмента):

[code] hermes skills install devops/kanban-worker [/code]

Диспетчер также автоматически передаёт --skills kanban-worker при запуске каждого рабочего, так что у рабочего всегда есть библиотека паттернов, даже если конфигурация навыков профиля по умолчанию не включает его.

Прикрепление дополнительных навыков к конкретной задаче​

Иногда одной задаче требуется специализированный контекст, который профиль исполнителя не носит по умолчанию — задача перевода, требующая навык translation, задача ревью, требующая github-code-review, аудит безопасности, требующий security-pr-audit. Вместо редактирования профиля исполнителя каждый раз прикрепите навыки непосредственно к задаче.

От агента-оркестратора (обычный случай — один агент направляет работу другому), используйте массив skills инструмента kanban_create:

[code] kanban_create( title="translate README to Japanese", assignee="linguist", skills=["translation"], )

kanban_create(
    title="audit auth flow",
    assignee="reviewer",
    skills=["security-pr-audit", "github-code-review"],
)

[/code]

От человека (CLI / слэш-команда), повторяйте --skill для каждого:

[code] hermes kanban create "translate README to Japanese" \ --assignee linguist \ --skill translation

hermes kanban create "audit auth flow" \
    --assignee reviewer \
    --skill security-pr-audit \
    --skill github-code-review

[/code]

Из панели управления, введите навыки через запятую в поле skills встроенной формы создания.

Эти навыки дополняют встроенный kanban-worker — диспетчер передаёт по одному флагу --skills <name> для каждого (и для встроенного), так что рабочий запускается со всеми загруженными. Имена навыков должны соответствовать навыкам, которые действительно установлены в профиле исполнителя (выполните hermes skills list, чтобы увидеть доступные); установка на лету не производится.

Навык оркестратора​

Воспитанный оркестратор не делает работу сам. Он декомпозирует цель пользователя на задачи, связывает их, назначает каждую специалисту и отступает. Навык kanban-orchestrator кодирует это как паттерны вызовов инструментов: правила анти-соблазна, стандартный список специалистов (researcher, writer, analyst, backend-eng, reviewer, ops) и плейбук декомпозиции, основанный на kanban_create / kanban_link / kanban_comment.

Канонический оборот оркестратора (два параллельных исследователя, передающих писателю):

[code] # Goal from user: "draft a launch post on the ICP funding landscape" kanban_create(title="research ICP funding, NA angle", assignee="researcher-a", body="…") # → t_r1 kanban_create(title="research ICP funding, EU angle", assignee="researcher-b", body="…") # → t_r2 kanban_create( title="synthesize ICP funding research into launch post draft", assignee="writer", parents=["t_r1", "t_r2"], # promoted to 'ready' when both researchers complete body="one-pager, neutral tone, cite sources inline", ) # → t_w1 # Optional: add cross-cutting deps discovered later without re-creating tasks kanban_link(parent_id="t_r1", child_id="t_followup") kanban_complete( summary="decomposed into 2 parallel research tasks → 1 synthesis task; writer starts when both researchers finish", ) [/code]

Загрузите его в свой профиль оркестратора:

[code] hermes skills install devops/kanban-orchestrator [/code]

Для лучшего результата объедините его с профилем, чьи наборы инструментов ограничены операциями с доской (kanban, gateway, memory), чтобы оркестратор буквально не мог выполнять задачи реализации, даже если попытается.

Панель управления (GUI)​

CLI-команда /kanban и слэш-команда достаточны для безголового управления доской, но визуальная доска часто является правильным интерфейсом для людей-в-цикле: триаж, межпрофильный надзор, чтение веток комментариев и перетаскивание карточек между колонками. Hermes поставляет это как встроенный плагин панели управления в plugins/kanban/ — не основная функция, не отдельный сервис — следуя модели, описанной в Расширение панели управления.

Откройте его с помощью:

[code] hermes kanban init # one-time: create kanban.db if not already present hermes dashboard # "Kanban" tab appears in the nav, after "Skills" [/code]

Что даёт плагин​

  • Вкладка Kanban, показывающая одну колонку на статус: triage, todo, ready, running, blocked, done (плюс archived, когда переключатель включён).
  • triage — это парковочная колонка для сырых идей, которые спецификатор должен проработать. Задачи, созданные с hermes kanban create --triage (или через встроенное создание в колонке Triage), попадают сюда, и диспетчер не трогает их, пока человек или спецификатор не переведёт их в todo / ready.
  • Карточки показывают id задачи, заголовок, значок приоритета, тег арендатора, назначенный профиль, количество комментариев/связей, индикатор прогресса (N/M дочерних задач выполнено, когда у задачи есть зависимые) и «создано N назад». Флажок на каждой карточке включает множественный выбор.
  • Полосы по профилям внутри Running — флажок на панели инструментов включает подгруппировку колонки Running по исполнителю.
  • Живые обновления через WebSocket — плагин отслеживает таблицу task_events (только добавление) с коротким интервалом опроса; доска отражает изменения в тот момент, когда любой профиль (CLI, шлюз или другая вкладка панели управления) действует. Перезагрузки дебаунсятся, чтобы всплеск событий вызывал единую повторную выборку.
  • Перетаскивание карточек между колонками для изменения статуса. Сброс отправляет PATCH /api/plugins/kanban/tasks/:id, который проходит через тот же код kanban_db, что и CLI — три интерфейса никогда не могут разойтись. Перемещения в деструктивные статусы (done, archived, blocked) запрашивают подтверждение. Сенсорные устройства используют запасной вариант на основе указателя, так что доской можно пользоваться с планшета.
  • Встроенное создание — нажмите + в заголовке любой колонки, чтобы ввести заголовок, исполнителя, приоритет и (опционально) родительскую задачу из выпадающего списка всех существующих задач. Создание из колонки Triage автоматически помещает новую задачу в триаж.
  • Множественный выбор с массовыми действиями — shift/ctrl-клик по карточке или установка её флажка добавляет её в выделение. Панель массовых действий появляется вверху с пакетными переходами статусов, архивированием и переназначением (через выпадающий список профилей или «(снять назначение)»). Деструктивные пакеты сначала запрашивают подтверждение. Частичные ошибки по каждому id сообщаются без прерывания остальных.
  • Клик по карточке (без shift/ctrl) открывает боковую панель (Escape или клик вне закрывает) с:
  • Редактируемым заголовком — нажмите на заголовок, чтобы переименовать.
  • Редактируемым исполнителем / приоритетом — нажмите на строку метаданных, чтобы изменить.
  • Редактируемым описанием — по умолчанию отображается в markdown (заголовки, жирный, курсив, строчный код, блоки кода, ссылки http(s) / mailto:, маркированные списки), с кнопкой «редактировать», которая заменяет на textarea. Markdown-рендеринг — это крошечный, безопасный от XSS рендерер — каждая подстановка выполняется над HTML-экранированным вводом, пропускаются только ссылки http(s) / mailto:, и всегда устанавливаются target="_blank" + rel="noopener noreferrer".
  • Редактором зависимостей — список чипов родителей и потомков, каждый с × для открепления, плюс выпадающие списки всех остальных задач для добавления нового родителя или потомка. Попытки создать цикл отклоняются на серверной стороне с чётким сообщением.
  • Строкой действий со статусом (→ triage / → ready / → running / block / unblock / complete / archive) с запросами подтверждения для деструктивных переходов.
  • Разделом результата (также отображается в markdown), веткой комментариев с отправкой по Enter, последними 20 событиями.
  • Фильтры панели инструментов — полнотекстовый поиск, выпадающий список арендаторов (по умолчанию dashboard.kanban.default_tenant из config.yaml), выпадающий список исполнителей, переключатель «показать архивные», переключатель «полосы по профилям» и кнопка Nudge dispatcher, чтобы не ждать следующего тика в 60 с.

Визуально цель — знакомый макет Linear / Fusion: тёмная тема, заголовки колонок со счётчиками, цветные точки статуса, чип-таблетки для приоритета и арендатора. Плагин читает только CSS-переменные темы (--color-*, --radius, --font-mono, ...), поэтому он автоматически перекрашивается под любую активную тему панели управления.

Архитектура​

GUI — это строго слой чтения-через-БД + записи-через-kanban_db без собственной доменной логики:

[code] ┌────────────────────────┐ WebSocket (tails task_events) │ React SPA (plugin) │ ◀──────────────────────────────────┐ │ HTML5 drag-and-drop │ │ └──────────┬─────────────┘ │ │ REST over fetchJSON │ ▼ │ ┌────────────────────────┐ writes call kanban_db.* │ │ FastAPI router │ directly — same code path │ │ plugins/kanban/ │ the CLI /kanban verbs use │ │ dashboard/plugin_api.py │ └──────────┬─────────────┘ │ │ │ ▼ │ ┌────────────────────────┐ │ │ ~/.hermes/kanban.db │ ───── append task_events ──────────┘ │ (WAL, shared) │ └────────────────────────┘ [/code]

REST-поверхность​

Все маршруты смонтированы в /api/plugins/kanban/ и защищены эфемерным токеном сессии панели управления:

Метод Путь Назначение
GET /board?tenant=<name>&include_archived=… Полная доска, сгруппированная по колонкам статуса, плюс арендаторы и исполнители для фильтров
GET /tasks/:id Задача + комментарии + события + связи
POST /tasks Создать (обёртка над kanban_db.create_task, принимает triage: bool и parents: [id, …])
PATCH /tasks/:id Статус / исполнитель / приоритет / заголовок / тело / результат
POST /tasks/bulk Применить тот же патч (статус / архив / исполнитель / приоритет) ко всем id в ids. Частичные ошибки сообщаются без прерывания остальных
POST /tasks/:id/comments Добавить комментарий
POST /links Добавить зависимость (parent_id → child_id)
DELETE /links?parent_id=…&child_id=… Удалить зависимость
POST /dispatch?max=…&dry_run=… Подтолкнуть диспетчера — пропустить ожидание в 60 с
GET /config Прочитать настройки dashboard.kanban из config.yaml — default_tenant, lane_by_profile, include_archived_by_default, render_markdown
WS /events?since=<event_id> Живой поток строк task_events

Каждый обработчик — это тонкая обёртка — плагин составляет ~700 строк Python (роутер + WebSocket-отслеживание + пакетный обработчик + читатель конфигурации) и не добавляет новой бизнес-логики. Маленький хелпер _conn() автоматически инициализирует kanban.db при каждом чтении и записи, так что свежая установка работает, открыл ли пользователь сначала панель управления, обратился напрямую к REST API или выполнил hermes kanban init.

Конфигурация панели управления​

Любой из этих ключей в разделе dashboard.kanban в ~/.hermes/config.yaml изменяет настройки по умолчанию для вкладки — плагин читает их при загрузке через GET /config:

[code] dashboard: kanban: default_tenant: acme # preselects the tenant filter lane_by_profile: true # default for the "lanes by profile" toggle include_archived_by_default: false render_markdown: true # set false for plain

 rendering
[/code]

Каждый ключ опционален и возвращается к показанному значению по умолчанию.

Модель безопасности​

Промежуточное ПО HTTP-аутентификации панели управления явно пропускает /api/plugins/ — маршруты плагинов неаутентифицированы по замыслу, потому что панель управления по умолчанию привязывается к localhost. Это означает, что REST-поверхность kanban доступна с любого процесса на хосте.

WebSocket делает один дополнительный шаг: он требует эфемерный токен сессии панели управления как параметр запроса ?token=… (браузеры не могут установить Authorization на запросе обновления), следуя паттерну, используемому встроенным PTY-мостом в браузере.

Если вы запустите hermes dashboard --host 0.0.0.0, каждый маршрут плагина — включая kanban — становится доступен из сети. Не делайте этого на общем хосте. Доска содержит тела задач, комментарии и пути к рабочим областям; атакующий, получивший доступ к этим маршрутам, получает доступ на чтение ко всей вашей поверхности совместной работы, а также может создавать, переназначать и архивировать задачи.

Задачи в ~/.hermes/kanban.db намеренно агностичны к профилям (в этом и заключается координационный примитив). Если вы откроете панель управления с помощью hermes -p <profile> dashboard, доска всё равно покажет задачи, созданные любым другим профилем на хосте. Один и тот же пользователь владеет всеми профилями, но об этом стоит знать, если сосуществуют несколько персон.

Живые обновления​

task_events — это таблица SQLite только на добавление с монотонным id. Конечная точка WebSocket хранит последний просмотренный id события каждого клиента и отправляет новые строки по мере их появления. Когда прибывает всплеск событий, фронтенд перезагружает (очень дешёвую) конечную точку доски — проще и правильнее, чем пытаться обновлять локальное состояние из каждого типа событий. Режим WAL означает, что цикл чтения никогда не блокирует транзакции захвата BEGIN IMMEDIATE диспетчера.

Расширение​

Плагин использует стандартный контракт плагинов панели управления Hermes — см. Расширение панели управления для полного справочника манифеста, слотов оболочки, слотов с областью видимости страницы и SDK плагинов. Дополнительные колонки, пользовательский хром карточек, макеты с фильтрацией по арендатору или полные замены tab.override могут быть выражены без форка этого плагина.

Чтобы отключить без удаления: добавьте dashboard.plugins.kanban.enabled: false в config.yaml (или удалите plugins/kanban/dashboard/manifest.json).

Граница области ответственности​

GUI намеренно тонкий. Всё, что делает плагин, доступно из CLI; плагин просто делает это удобным для людей. Автоназначение, бюджеты, шлюзы управления и представления оргструктуры остаются в пользовательском пространстве — профиль-роутер, другой плагин или повторное использование tools/approval.py — именно как перечислено в разделе «вне области ответственности» спецификации дизайна.

Справочник CLI-команд​

Это поверхность, которую вы (или скрипты, cron, панель управления) используете для управления доской. Рабочие, запущенные внутри диспетчера, используют поверхность инструментов kanban_* для тех же операций — CLI здесь и инструменты там проходят через kanban_db, поэтому две поверхности согласованы по построению.

[code] hermes kanban init # create kanban.db + print daemon hint hermes kanban create "" [--body ...] [--assignee <profile>] [--parent <id>]... [--tenant <name>] [--workspace scratch|worktree|dir:<path>] [--priority N] [--triage] [--idempotency-key KEY] [--max-runtime 30m|2h|1d|<seconds>] [--skill <name>]... [--json] hermes kanban list [--mine] [--assignee P] [--status S] [--tenant T] [--archived] [--json] hermes kanban show <id> [--json] hermes kanban assign <id> <profile> # or 'none' to unassign hermes kanban link <parent_id> <child_id> hermes kanban unlink <parent_id> <child_id> hermes kanban claim <id> [--ttl SECONDS] hermes kanban comment <id> "<text>" [--author NAME]</p> <div class="highlight"><pre><span></span><code># Bulk verbs — accept multiple ids: hermes kanban complete <id>... [--result "..."] hermes kanban block <id> "<reason>" [--ids <id>...] hermes kanban unblock <id>... hermes kanban archive <id>... hermes kanban tail <id> # follow a single task's event stream hermes kanban watch [--assignee P] [--tenant T] # live stream ALL events to the terminal [--kinds completed,blocked,…] [--interval SECS] hermes kanban heartbeat <id> [--note "..."] # worker liveness signal for long ops hermes kanban runs <id> [--json] # attempt history (one row per run) hermes kanban assignees [--json] # profiles on disk + per-assignee task counts hermes kanban dispatch [--dry-run] [--max N] # one-shot pass [--failure-limit N] [--json] hermes kanban daemon --force # DEPRECATED — standalone dispatcher (use `hermes gateway start` instead) [--failure-limit N] [--pidfile PATH] [-v] hermes kanban stats [--json] # per-status + per-assignee counts hermes kanban log <id> [--tail BYTES] # worker log from ~/.hermes/kanban/logs/ hermes kanban notify-subscribe <id> # gateway bridge hook (used by /kanban in the gateway) --platform <name> --chat-id <id> [--thread-id <id>] [--user-id <id>] hermes kanban notify-list [<id>] [--json] hermes kanban notify-unsubscribe <id> --platform <name> --chat-id <id> [--thread-id <id>] hermes kanban context <id> # what a worker sees hermes kanban gc [--event-retention-days N] # workspaces + old events + old logs [--log-retention-days N] </code></pre></div> <p>[/code]</p> <p>Все команды также доступны как слэш-команда в интерактивном CLI и в шлюзе обмена сообщениями (см. раздел <a href="#kanban-slash-command"><code>/kanban</code> слэш-команда</a> ниже).</p> <h2 id="-kanban">Слэш-команда <code>/kanban</code><a href="#kanban-slash-command" title="Прямая ссылка на раздел «Слэш-команда /kanban»">​</a><a class="headerlink" href="#-kanban" title="Permanent link">¶</a></h2> <p>Каждый глагол <code>hermes kanban <action></code> также доступен как <code>/kanban <action></code> — из интерактивной сессии <code>hermes chat</code> <strong>и</strong> с любой платформы шлюза (Telegram, Discord, Slack, WhatsApp, Signal, Matrix, Mattermost, email, SMS). Оба интерфейса вызывают одну и ту же точку входа <code>hermes_cli.kanban.run_slash()</code>, которая использует то же дерево argparse <code>hermes kanban</code>, поэтому поверхность аргументов, флаги и формат вывода идентичны через CLI, <code>/kanban</code> и <code>hermes kanban</code>. Вам не нужно покидать чат, чтобы управлять доской.</p> <p>[code] /kanban list /kanban show t_abcd /kanban create "write launch post" --assignee writer --parent t_research /kanban comment t_abcd "looks good, ship it" /kanban unblock t_abcd /kanban dispatch --max 3 [/code]</p> <p>Заключайте многословные аргументы в кавычки так же, как в оболочке — <code>run_slash</code> парсит остаток строки с помощью <code>shlex.split</code>, так что <code>"..."</code> и <code>'...'</code> оба работают.</p> <h3 id="kanban">Использование во время выполнения: <code>/kanban</code> обходит защиту от запущенного агента<a href="#mid-run-usage-kanban-bypasses-the-running-agent-guard" title="Прямая ссылка на раздел «Использование во время выполнения: /kanban обходит защиту от запущенного агента»">​</a><a class="headerlink" href="#kanban" title="Permanent link">¶</a></h3> <p>Шлюз обычно ставит в очередь слэш-команды и сообщения пользователя, пока агент ещё думает — это предотвращает случайный запуск второго оборота, пока первый в полёте. <strong><code>/kanban</code> явно исключён из этой защиты.</strong> Доска живёт в <code>~/.hermes/kanban.db</code>, а не в состоянии работающего агента, поэтому чтение (<code>list</code>, <code>show</code>, <code>context</code>, <code>tail</code>, <code>watch</code>, <code>stats</code>, <code>runs</code>) и запись (<code>comment</code>, <code>unblock</code>, <code>block</code>, <code>assign</code>, <code>archive</code>, <code>create</code>, <code>link</code>, …) проходят немедленно, даже в середине оборота.</p> <p>В этом и заключается весь смысл разделения:</p> <ul> <li>Рабочий заблокирован в ожидании коллеги → вы отправляете <code>/kanban unblock t_abcd</code> со своего телефона, и диспетчер подхватывает коллегу на следующем тике. Заблокированный рабочий не прерывается — он просто перестаёт быть заблокированным.</li> <li>Вы заметили карточку, которой нужен человеческий контекст → <code>/kanban comment t_xyz "используй схему 2026, а не 2025"</code> попадает в ветку задачи, и <em>следующий</em> запуск этой задачи прочитает его в <code>kanban_show()</code>.</li> <li>Вы хотите узнать, чем занимается ваш флот, не останавливая оркестратора → <code>/kanban list --mine</code> или <code>/kanban stats</code> просматривает доску, не трогая ваш основной разговор.</li> </ul> <h3 id="kanban-create">Автоподписка при <code>/kanban create</code> (только шлюз)<a href="#auto-subscribe-on-kanban-create-gateway-only" title="Прямая ссылка на раздел «Автоподписка при /kanban create (только шлюз)»">​</a><a class="headerlink" href="#kanban-create" title="Permanent link">¶</a></h3> <p>Когда вы создаёте задачу из шлюза с помощью <code>/kanban create "…"</code>, исходный чат (платформа + id чата + id ветки) автоматически подписывается на терминальные события этой задачи (<code>completed</code>, <code>blocked</code>, <code>gave_up</code>, <code>crashed</code>, <code>timed_out</code>). Вы получите одно сообщение на каждое терминальное событие — включая первую строку сводки результата рабочего при <code>completed</code> — без необходимости опрашивать или запоминать id задачи.</p> <p>[code] you> /kanban create "transcribe today's podcast" --assignee transcriber bot> Created t_9fc1a3 (ready, assignee=transcriber) (subscribed — you'll be notified when t_9fc1a3 completes or blocks)</p> <div class="highlight"><pre><span></span><code>… ~8 minutes later … bot> ✓ t_9fc1a3 completed by transcriber transcribed 42 minutes, saved to podcast/2026-05-04.md </code></pre></div> <p>[/code]</p> <p>Подписки автоматически удаляются, как только задача достигает <code>done</code> или <code>archived</code>. Если вы скриптуете создание с <code>--json</code> (машинный вывод), автоподписка пропускается — предполагается, что скриптовые вызывающие хотят управлять подписками явно через <code>/kanban notify-subscribe</code>.</p> <h3 id="_19">Обрезание вывода в обмене сообщениями<a href="#output-truncation-in-messaging" title="Прямая ссылка на раздел «Обрезание вывода в обмене сообщениями»">​</a><a class="headerlink" href="#_19" title="Permanent link">¶</a></h3> <p>Платформы шлюза имеют практические ограничения на длину сообщения. Если <code>/kanban list</code>, <code>/kanban show</code> или <code>/kanban tail</code> выдают более ~3800 символов, ответ обрезается с нижним колонтитулом <code>… (truncated; use \</code>hermes kanban …` in your terminal for full output)`. У CLI-интерфейса такого ограничения нет.</p> <h3 id="_20">Автодополнение<a href="#autocomplete" title="Прямая ссылка на раздел «Автодополнение»">​</a><a class="headerlink" href="#_20" title="Permanent link">¶</a></h3> <p>В интерактивном CLI при вводе <code>/kanban</code> и нажатии Tab циклически перебирается встроенный список подкоманд (<code>list</code>, <code>ls</code>, <code>show</code>, <code>create</code>, <code>assign</code>, <code>link</code>, <code>unlink</code>, <code>claim</code>, <code>comment</code>, <code>complete</code>, <code>block</code>, <code>unblock</code>, <code>archive</code>, <code>tail</code>, <code>dispatch</code>, <code>context</code>, <code>init</code>, <code>gc</code>). Остальные глаголы, перечисленные в справочнике CLI выше (<code>watch</code>, <code>stats</code>, <code>runs</code>, <code>log</code>, <code>assignees</code>, <code>heartbeat</code>, <code>notify-subscribe</code>, <code>notify-list</code>, <code>notify-unsubscribe</code>, <code>daemon</code>), тоже работают — их просто ещё нет в списке подсказок автодополнения.</p> <h2 id="_21">Паттерны совместной работы<a href="#collaboration-patterns" title="Прямая ссылка на раздел «Паттерны совместной работы»">​</a><a class="headerlink" href="#_21" title="Permanent link">¶</a></h2> <p>Доска поддерживает эти восемь паттернов без каких-либо новых примитивов:</p> <table> <thead> <tr> <th>Паттерн</th> <th>Форма</th> <th>Пример</th> </tr> </thead> <tbody> <tr> <td><strong>P1 Разветвление</strong></td> <td>N братьев, та же роль</td> <td>«research 5 angles in parallel»</td> </tr> <tr> <td><strong>P2 Конвейер</strong></td> <td>цепочка ролей: разведчик → редактор → писатель</td> <td>сборка ежедневного брифинга</td> </tr> <tr> <td><strong>P3 Голосование / кворум</strong></td> <td>N братьев + 1 агрегатор</td> <td>3 исследователя → 1 рецензент выбирает</td> </tr> <tr> <td><strong>P4 Долгоиграющий журнал</strong></td> <td>тот же профиль + общая директория + cron</td> <td>хранилище Obsidian</td> </tr> <tr> <td><strong>P5 Человек в цикле</strong></td> <td>рабочий блокируется → пользователь комментирует → разблокировка</td> <td>неоднозначные решения</td> </tr> <tr> <td><strong>P6 <code>@упоминание</code></strong></td> <td>встроенная маршрутизация из прозы</td> <td><code>@reviewer look at this</code></td> </tr> <tr> <td><strong>P7 Рабочая область в рамках ветки</strong></td> <td><code>/kanban here</code> в ветке</td> <td>проектные ветки шлюза</td> </tr> <tr> <td><strong>P8 Фермерство</strong></td> <td>один профиль, N субъектов</td> <td>50 соцсетей</td> </tr> <tr> <td><strong>P9 Спецификатор триажа</strong></td> <td>сырая идея → <code>triage</code> → спецификатор расширяет тело → <code>todo</code></td> <td>«turn this one-liner into a spec' task»</td> </tr> </tbody> </table> <p>Для проработанных примеров каждого паттерна см. <code>docs/hermes-kanban-v1-spec.pdf</code>.</p> <h2 id="_22">Мультитенантное использование<a href="#multi-tenant-usage" title="Прямая ссылка на раздел «Мультитенантное использование»">​</a><a class="headerlink" href="#_22" title="Permanent link">¶</a></h2> <p>Когда один флот специалистов обслуживает несколько бизнесов, пометьте каждую задачу арендатором:</p> <p>[code] hermes kanban create "monthly report" \ --assignee researcher \ --tenant business-a \ --workspace dir:~/tenants/business-a/data/ [/code]</p> <p>Рабочие получают <code>$HERMES_TENANT</code> и пространство имён для своих записей памяти через префикс. Доска, диспетчер и определения профилей являются общими; данные изолированы.</p> <h2 id="_23">Уведомления шлюза<a href="#gateway-notifications" title="Прямая ссылка на раздел «Уведомления шлюза»">​</a><a class="headerlink" href="#_23" title="Permanent link">¶</a></h2> <p>Когда вы запускаете <code>/kanban create …</code> из шлюза (Telegram, Discord, Slack и т.д.), исходный чат автоматически подписывается на новую задачу. Фоновый уведомитель шлюза опрашивает <code>task_events</code> каждые несколько секунд и доставляет одно сообщение на каждое терминальное событие (<code>completed</code>, <code>blocked</code>, <code>gave_up</code>, <code>crashed</code>, <code>timed_out</code>) в этот чат. Для завершённых задач также отправляется первая строка <code>--result</code> рабочего, чтобы вы видели результат без необходимости выполнять <code>/kanban show</code>.</p> <p>Вы можете управлять подписками явно из CLI — полезно, когда скрипт / cron-задача хочет уведомить чат, из которого она не была создана:</p> <p>[code] hermes kanban notify-subscribe t_abcd \ --platform telegram --chat-id 12345678 --thread-id 7 hermes kanban notify-list hermes kanban notify-unsubscribe t_abcd \ --platform telegram --chat-id 12345678 --thread-id 7 [/code]</p> <p>Подписка удаляется автоматически, как только задача достигает <code>done</code> или <code>archived</code>; никакой очистки не требуется.</p> <h2 id="_24">Запуски — одна строка на попытку<a href="#runs--one-row-per-attempt" title="Прямая ссылка на раздел «Запуски — одна строка на попытку»">​</a><a class="headerlink" href="#_24" title="Permanent link">¶</a></h2> <p>Задача — это логическая единица работы; <strong>запуск</strong> — это одна попытка её выполнения. Когда диспетчер захватывает готовую задачу, он создаёт строку в <code>task_runs</code> и устанавливает <code>tasks.current_run_id</code> на неё. Когда эта попытка завершается — выполнена, заблокирована, упала, превысила таймаут, не удалась при запуске, возвращена — строка запуска закрывается с <code>outcome</code>, и указатель задачи очищается. Задача, которая была предпринята трижды, имеет три строки <code>task_runs</code>.</p> <p>Зачем две таблицы вместо простого изменения задачи: вам нужна <strong>полная история попыток</strong> для реальных посмертных анализов («вторая попытка ревьювера получила одобрение, третья слила»), и вам нужно чистое место для хранения метаданных каждой попытки — какие файлы изменились, какие тесты запускались, какие выводы отметил рецензент. Это факты запуска, а не факты задачи.</p> <p>Запуски также являются местом, где хранится <strong>структурированная передача</strong>. Когда рабочий завершает задачу (через <code>kanban_complete(...)</code>), он может передать:</p> <ul> <li><code>summary</code> (параметр инструмента) / <code>--summary</code> (CLI) — человеческая передача; попадает в запуск; downstream-потомки видят его в своём <code>build_worker_context</code>.</li> <li><code>metadata</code> (параметр инструмента) / <code>--metadata</code> (CLI) — свободный JSON-словарь в запуске; потомки видят его сериализованным вместе со сводкой.</li> <li><code>result</code> (параметр инструмента) / <code>--result</code> (CLI) — короткая строка лога, которая попадает в строку задачи (устаревшее поле, сохранено для обратной совместимости).</li> </ul> <p>Downstream-потомки читают сводку + метаданные самого последнего завершённого запуска для каждого родителя. Рабочие при повторных попытках читают предыдущие попытки своей задачи (результат, сводку, ошибку), чтобы не повторять путь, который уже не удался.</p> <p>[code] # What a worker actually does — a tool call, from inside the agent loop: kanban_complete( summary="implemented token bucket, keys on user_id with IP fallback, all tests pass", metadata={"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14}, result="rate limiter shipped", ) [/code]</p> <p>Та же передача доступна из CLI, когда вам (человеку) нужно закрыть задачу, которую рабочий не может — например, задачу, которая была abandonирована, или ту, которую вы отметили как выполненную вручную из панели управления:</p> <p>[code] hermes kanban complete t_abcd \ --result "rate limiter shipped" \ --summary "implemented token bucket, keys on user_id with IP fallback, all tests pass" \ --metadata '{"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14}'</p> <div class="highlight"><pre><span></span><code># Review the attempt history on a retried task: hermes kanban runs t_abcd # # OUTCOME PROFILE ELAPSED STARTED # 1 blocked worker 12s 2026-04-27 14:02 # → BLOCKED: need decision on rate-limit key # 2 completed worker 8m 2026-04-27 15:18 # → implemented token bucket, keys on user_id with IP fallback </code></pre></div> <p>[/code]</p> <p>Запуски отображаются на панели управления (раздел Run History в боковой панели, по одной цветной строке на попытку) и в REST API (<code>GET /api/plugins/kanban/tasks/:id</code> возвращает массив <code>runs[]</code>). <code>PATCH /api/plugins/kanban/tasks/:id</code> с <code>{status: "done", summary, metadata}</code> передаёт оба в ядро, так что кнопка «отметить как выполненное» в панели управления эквивалентна CLI. Строки <code>task_events</code> содержат <code>run_id</code>, к которому они принадлежат, чтобы UI мог группировать их по попыткам, а событие <code>completed</code> встраивает первую строку сводки в свою полезную нагрузку (ограничено 400 символами), чтобы уведомители шлюза могли отображать структурированные передачи без второго кругого запроса к SQL.</p> <p><strong>Предостережение о массовом закрытии.</strong> <code>hermes kanban complete a b c --summary X</code> отклоняется — структурированная передача выполняется для каждого запуска, поэтому копирование одной и той же сводки к N задачам почти всегда неправильно. Массовое закрытие <em>без</em> <code>--summary</code> / <code>--metadata</code> по-прежнему работает для обычного случая «я закончил кучу административных задач».</p> <p><strong>Возвращённые запуски при изменении статуса.</strong> Если вы перетащите запущенную задачу из <code>running</code> в панели управления (обратно в <code>ready</code> или сразу в <code>todo</code>) или архивируете задачу, которая всё ещё выполнялась, выполняющийся запуск закрывается с <code>outcome='reclaimed'</code> вместо того, чтобы остаться осиротевшим. Строка <code>task_runs</code> всегда находится в терминальном состоянии, когда <code>tasks.current_run_id</code> равно <code>NULL</code>, и наоборот — этот инвариант сохраняется через CLI, панель управления, диспетчер и уведомитель.</p> <p><strong>Синтетические запуски для никогда не захватывавшихся завершений.</strong> Завершение или блокировка задачи, которая никогда не была захвачена (например, человек закрывает задачу со статусом <code>ready</code> из панели управления со сводкой, или пользователь CLI выполняет <code>hermes kanban complete <ready-task> --summary X</code>) в противном случае потеряли бы передачу. Вместо этого ядро вставляет строку запуска нулевой длительности (<code>started_at == ended_at</code>), несущую сводку / метаданные / причину, так что история попыток остаётся полной. <code>run_id</code> события <code>completed</code> / <code>blocked</code> указывает на эту строку.</p> <p><strong>Живое обновление боковой панели.</strong> Когда поток событий WebSocket панели управления сообщает о новых событиях для задачи, которую пользователь сейчас просматривает, боковая панель перезагружается (через счётчик событий на задачу, встроенный в список зависимостей <code>useEffect</code>). Закрытие и повторное открытие больше не требуется, чтобы увидеть новую строку запуска или обновлённый результат.</p> <h3 id="_25">Прямая совместимость<a href="#forward-compatibility" title="Прямая ссылка на раздел «Прямая совместимость»">​</a><a class="headerlink" href="#_25" title="Permanent link">¶</a></h3> <p>Два nullable-столбца в <code>tasks</code> зарезервированы для маршрутизации рабочих процессов v2: <code>workflow_template_id</code> (к какому шаблону принадлежит эта задача) и <code>current_step_key</code> (какой шаг в этом шаблоне активен). Ядро v1 игнорирует их для маршрутизации, но позволяет клиентам их записывать, так что релиз v2 сможет добавить механизм маршрутизации без ещё одной миграции схемы.</p> <h2 id="_26">Справочник событий<a href="#event-reference" title="Прямая ссылка на раздел «Справочник событий»">​</a><a class="headerlink" href="#_26" title="Permanent link">¶</a></h2> <p>Каждый переход добавляет строку в <code>task_events</code>. Каждая строка содержит опциональный <code>run_id</code>, чтобы UI мог группировать события по попыткам. Виды группируются в три кластера для удобства фильтрации (<code>hermes kanban watch --kinds completed,gave_up,timed_out</code>):</p> <p><strong>Жизненный цикл</strong> (что изменилось в задаче как логической единице):</p> <table> <thead> <tr> <th>Вид</th> <th>Полезная нагрузка</th> <th>Когда</th> </tr> </thead> <tbody> <tr> <td><code>created</code></td> <td><code>{assignee, status, parents, tenant}</code></td> <td>Задача вставлена. <code>run_id</code> равен <code>NULL</code>.</td> </tr> <tr> <td><code>promoted</code></td> <td>—</td> <td><code>todo → ready</code>, потому что все родители достигли <code>done</code>. <code>run_id</code> равен <code>NULL</code>.</td> </tr> <tr> <td><code>claimed</code></td> <td><code>{lock, expires, run_id}</code></td> <td>Диспетчер атомарно захватил задачу <code>ready</code> для запуска.</td> </tr> <tr> <td><code>completed</code></td> <td><code>{result_len, summary?}</code></td> <td>Рабочий записал <code>--result</code> / <code>--summary</code>, и задача достигла <code>done</code>. <code>summary</code> — это первая строка передачи (ограничение 400 символов); полная версия хранится в строке запуска. Если <code>complete_task</code> вызван для никогда не захваченной задачи с полями передачи, синтезируется запуск нулевой длительности, чтобы <code>run_id</code> всё равно на что-то указывал.</td> </tr> <tr> <td><code>blocked</code></td> <td><code>{reason}</code></td> <td>Рабочий или человек перевёл задачу в <code>blocked</code>. Синтезирует запуск нулевой длительности при вызове для никогда не захваченной задачи с <code>--reason</code>.</td> </tr> <tr> <td><code>unblocked</code></td> <td>—</td> <td><code>blocked → ready</code>, либо вручную, либо через <code>/unblock</code>. <code>run_id</code> равен <code>NULL</code>.</td> </tr> <tr> <td><code>archived</code></td> <td>—</td> <td>Скрыта из представления доски по умолчанию. Если задача всё ещё выполнялась, содержит <code>run_id</code> запуска, который был возвращён как побочный эффект.</td> </tr> </tbody> </table> <p><strong>Редактирование</strong> (изменения, инициированные человеком, не являющиеся переходами):</p> <table> <thead> <tr> <th>Вид</th> <th>Полезная нагрузка</th> <th>Когда</th> </tr> </thead> <tbody> <tr> <td><code>assigned</code></td> <td><code>{assignee}</code></td> <td>Исполнитель изменён (включая снятие назначения).</td> </tr> <tr> <td><code>edited</code></td> <td><code>{fields}</code></td> <td>Заголовок или тело обновлены.</td> </tr> <tr> <td><code>reprioritized</code></td> <td><code>{priority}</code></td> <td>Приоритет изменён.</td> </tr> <tr> <td><code>status</code></td> <td><code>{status}</code></td> <td>Перетаскивание на панели управления записало статус напрямую (например, <code>todo → ready</code>). Содержит <code>run_id</code> запуска, который был возвращён при перетаскивании из <code>running</code>; в противном случае <code>run_id</code> равен NULL.</td> </tr> </tbody> </table> <p><strong>Телеметрия рабочего</strong> (о процессе выполнения, а не о логической задаче):</p> <table> <thead> <tr> <th>Вид</th> <th>Полезная нагрузка</th> <th>Когда</th> </tr> </thead> <tbody> <tr> <td><code>spawned</code></td> <td><code>{pid}</code></td> <td>Диспетчер успешно запустил процесс рабочего.</td> </tr> <tr> <td><code>heartbeat</code></td> <td><code>{note?}</code></td> <td>Рабочий вызвал <code>hermes kanban heartbeat $TASK</code> для сигнала жизнеспособности во время долгих операций.</td> </tr> <tr> <td><code>reclaimed</code></td> <td><code>{stale_lock}</code></td> <td>TTL захвата истёк без завершения; задача возвращается в <code>ready</code>.</td> </tr> <tr> <td><code>crashed</code></td> <td><code>{pid, claimer}</code></td> <td>PID рабочего больше не жив, но TTL ещё не истёк.</td> </tr> <tr> <td><code>timed_out</code></td> <td><code>{pid, elapsed_seconds, limit_seconds, sigkill}</code></td> <td>Превышено <code>max_runtime_seconds</code>; диспетчер отправил SIGTERM (затем SIGKILL через 5 с льготного периода) и повторно поставил в очередь.</td> </tr> <tr> <td><code>spawn_failed</code></td> <td><code>{error, failures}</code></td> <td>Одна попытка запуска не удалась (отсутствует PATH, рабочая область не монтируется, ...). Счётчик увеличивается; задача возвращается в <code>ready</code> для повтора.</td> </tr> <tr> <td><code>gave_up</code></td> <td><code>{failures, error}</code></td> <td>Автоматический выключатель сработал после N последовательных <code>spawn_failed</code>. Задача автоматически блокируется с последней ошибкой. N по умолчанию = 5; переопределяется через <code>--failure-limit</code>.</td> </tr> </tbody> </table> <p><code>hermes kanban tail <id></code> показывает их для одной задачи. <code>hermes kanban watch</code> транслирует их по всей доске.</p> <h2 id="_27">Вне области ответственности<a href="#out-of-scope" title="Прямая ссылка на раздел «Вне области ответственности»">​</a><a class="headerlink" href="#_27" title="Permanent link">¶</a></h2> <p>Kanban намеренно однопользовательский. <code>~/.hermes/kanban.db</code> — это локальный файл SQLite, и диспетчер запускает рабочих на той же машине. Работа общей доски на двух хостах не поддерживается — нет координационного примитива для «рабочий X на хосте A, рабочий Y на хосте B», и путь обнаружения сбоев предполагает, что PID локальны для хоста. Если вам нужно несколько хостов, запустите независимую доску на каждом хосте и используйте <code>delegate_task</code> / очередь сообщений для их моста.</p> <h2 id="_28">Спецификация дизайна<a href="#design-spec" title="Прямая ссылка на раздел «Спецификация дизайна»">​</a><a class="headerlink" href="#_28" title="Permanent link">¶</a></h2> <p>Полный дизайн — архитектура, корректность конкурентности, сравнение с другими системами, план реализации, риски, открытые вопросы — находится в <code>docs/hermes-kanban-v1-spec.pdf</code>. Прочитайте его перед отправкой любого PR, изменяющего поведение.</p> <ul> <li><a href="#two-surfaces-the-model-talks-through-tools-you-talk-through-the-cli">Два интерфейса: модель общается через инструменты, вы — через CLI</a></li> <li><a href="#kanban-vs-delegate_task">Kanban vs <code>delegate_task</code></a></li> <li><a href="#core-concepts">Основные понятия</a></li> <li><a href="#boards-multi-project">Доски (мультипроект)</a></li> <li><a href="#managing-boards-from-the-cli">Управление досками через CLI</a></li> <li><a href="#managing-boards-from-the-dashboard">Управление досками через панель управления</a></li> <li><a href="#quick-start">Быстрый старт</a></li> <li><a href="#gateway-embedded-dispatcher-default">Диспетчер, встроенный в шлюз (по умолчанию)</a></li> <li><a href="#idempotent-create-for-automation--webhooks">Идемпотентное создание (для автоматизации / вебхуков)</a></li> <li><a href="#bulk-cli-verbs">Массовые CLI-команды</a></li> <li><a href="#how-workers-interact-with-the-board">Как рабочие взаимодействуют с доской</a></li> <li><a href="#why-tools-instead-of-shelling-to-hermes-kanban">Почему инструменты вместо вызова <code>hermes kanban</code> в оболочке</a></li> <li><a href="#recommended-handoff-evidence">Рекомендуемые свидетельства передачи</a></li> <li><a href="#the-worker-skill">Навык рабочего</a></li> <li><a href="#pinning-extra-skills-to-a-specific-task">Прикрепление дополнительных навыков к конкретной задаче</a></li> <li><a href="#the-orchestrator-skill">Навык оркестратора</a></li> <li><a href="#dashboard-gui">Панель управления (GUI)</a></li> <li><a href="#what-the-plugin-gives-you">Что даёт плагин</a></li> <li><a href="#architecture">Архитектура</a></li> <li><a href="#rest-surface">REST-поверхность</a></li> <li><a href="#dashboard-config">Конфигурация панели управления</a></li> <li><a href="#security-model">Модель безопасности</a></li> <li><a href="#live-updates">Живые обновления</a></li> <li><a href="#extending-it">Расширение</a></li> <li><a href="#scope-boundary">Граница области ответственности</a></li> <li><a href="#cli-command-reference">Справочник CLI-команд</a></li> <li><a href="#kanban-slash-command"><code>/kanban</code> слэш-команда</a></li> <li><a href="#mid-run-usage-kanban-bypasses-the-running-agent-guard">Использование во время выполнения: <code>/kanban</code> обходит защиту от запущенного агента</a></li> <li><a href="#auto-subscribe-on-kanban-create-gateway-only">Автоподписка при <code>/kanban create</code> (только шлюз)</a></li> <li><a href="#output-truncation-in-messaging">Обрезание вывода в обмене сообщениями</a></li> <li><a href="#autocomplete">Автодополнение</a></li> <li><a href="#collaboration-patterns">Паттерны совместной работы</a></li> <li><a href="#multi-tenant-usage">Мультитенантное использование</a></li> <li><a href="#gateway-notifications">Уведомления шлюза</a></li> <li><a href="#runs--one-row-per-attempt">Запуски — одна строка на попытку</a></li> <li><a href="#forward-compatibility">Прямая совместимость</a></li> <li><a href="#event-reference">Справочник событий</a></li> <li><a href="#out-of-scope">Вне области ответственности</a></li> <li><a href="#design-spec">Спецификация дизайна</a></li> </ul> <!-- Source: https://hermes-agent.nousresearch.com/docs/user-guide/features/kanban --> </article> </div> <script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script> <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script> </div> <button type="button" class="md-top md-icon" data-md-component="top" hidden> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg> К началу </button> </main> <footer class="md-footer"> <div class="md-footer-meta md-typeset"> <div class="md-footer-meta__inner md-grid"> <div class="md-copyright"> <div class="md-copyright__highlight"> Copyright © 2025-2026 Nous Research — перевод сообщества </div> </div> <div class="md-social"> <a href="https://github.com/perejaslav/hermes-docs-ru" target="_blank" rel="noopener" title="github.com" class="md-social__link"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg> </a> <a href="https://discord.gg/NousResearch" target="_blank" rel="noopener" title="discord.gg" class="md-social__link"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M492.5 69.8c-.2-.3-.4-.6-.8-.7-38.1-17.5-78.4-30-119.7-37.1-.4-.1-.8 0-1.1.1s-.6.4-.8.8c-5.5 9.9-10.5 20.2-14.9 30.6-44.6-6.8-89.9-6.8-134.4 0-4.5-10.5-9.5-20.7-15.1-30.6-.2-.3-.5-.6-.8-.8s-.7-.2-1.1-.2C162.5 39 122.2 51.5 84.1 69c-.3.1-.6.4-.8.7C7.1 183.5-13.8 294.6-3.6 404.2c0 .3.1.5.2.8s.3.4.5.6c44.4 32.9 94 58 146.8 74.2.4.1.8.1 1.1 0s.7-.4.9-.7c11.3-15.4 21.4-31.8 30-48.8.1-.2.2-.5.2-.8s0-.5-.1-.8-.2-.5-.4-.6-.4-.3-.7-.4c-15.8-6.1-31.2-13.4-45.9-21.9-.3-.2-.5-.4-.7-.6s-.3-.6-.3-.9 0-.6.2-.9.3-.5.6-.7c3.1-2.3 6.2-4.7 9.1-7.1.3-.2.6-.4.9-.4s.7 0 1 .1c96.2 43.9 200.4 43.9 295.5 0 .3-.1.7-.2 1-.2s.7.2.9.4c2.9 2.4 6 4.9 9.1 7.2.2.2.4.4.6.7s.2.6.2.9-.1.6-.3.9-.4.5-.6.6c-14.7 8.6-30 15.9-45.9 21.8-.2.1-.5.2-.7.4s-.3.4-.4.7-.1.5-.1.8.1.5.2.8c8.8 17 18.8 33.3 30 48.8.2.3.6.6.9.7s.8.1 1.1 0c52.9-16.2 102.6-41.3 147.1-74.2.2-.2.4-.4.5-.6s.2-.5.2-.8c12.3-126.8-20.5-236.9-86.9-334.5zm-302 267.7c-29 0-52.8-26.6-52.8-59.2s23.4-59.2 52.8-59.2c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.4 59.2-52.8 59.2m195.4 0c-29 0-52.8-26.6-52.8-59.2s23.4-59.2 52.8-59.2c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.2 59.2-52.8 59.2"/></svg> </a> </div> </div> </div> </footer> </div> <div class="md-dialog" data-md-component="dialog"> <div class="md-dialog__inner md-typeset"></div> </div> <script id="__config" type="application/json">{"annotate": null, "base": "../../../..", "features": ["navigation.instant", "navigation.tracking", "navigation.indexes", "navigation.top", "search.highlight", "search.suggest", "content.code.copy", "content.tabs.link"], "search": "../../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "\u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0432 \u0431\u0443\u0444\u0435\u0440", "clipboard.copy": "\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u0431\u0443\u0444\u0435\u0440", "search.result.more.one": "\u0415\u0449\u0451 1 \u043d\u0430 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435", "search.result.more.other": "\u0415\u0449\u0451 # \u043d\u0430 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435", "search.result.none": "\u0421\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e", "search.result.one": "\u041d\u0430\u0439\u0434\u0435\u043d\u043e 1 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435", "search.result.other": "\u041d\u0430\u0439\u0434\u0435\u043d\u043e \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439: #", "search.result.placeholder": "\u041d\u0430\u0447\u043d\u0438\u0442\u0435 \u043f\u0435\u0447\u0430\u0442\u0430\u0442\u044c \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430", "search.result.term.missing": "\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442", "select.version": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u0435\u0440\u0441\u0438\u044e"}, "version": null}</script> <script src="../../../../assets/javascripts/bundle.d7400e89.min.js"></script> </body> </html>