Pular para o conteúdo principal

Documentação oficial desta página · v0.20.6

Kanban — colaboração multiagente entre perfis

Quer um passo a passo? Leia o tutorial do Kanban — quatro histórias de uso (dev solo, frota em série, pipeline por papéis com repetição, disjuntor) com capturas de tela do painel em cada uma. Esta página é a referência; o tutorial é a narrativa.

O Kanban do Hermes é um quadro de tarefas durável, compartilhado por todos os seus perfis do Hermes, que deixa vários agentes nomeados colaborarem sem enxames frágeis de subagentes dentro do mesmo processo. Toda tarefa é uma linha em ~/.hermes/kanban.db; todo repasse é uma linha que qualquer um pode ler e escrever; todo worker é um processo do sistema operacional inteiro, com identidade própria.

Duas superfícies: o modelo fala por ferramentas, você fala pela CLI​

O quadro tem duas portas de entrada, ambas apoiadas no mesmo ~/.hermes/kanban.db:

  • Os agentes conduzem o quadro por um conjunto dedicado de ferramentas 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. O dispatcher já cria cada worker com essas ferramentas no schema; perfis orquestradores também podem habilitar o conjunto kanban explicitamente. O modelo lê e roteia tarefas chamando ferramentas diretamente, e não rodando hermes kanban no shell. Veja Como os workers interagem com o quadro mais abaixo.
  • Você (e scripts, e o cron) conduzem o quadro por hermes kanban … na CLI, por /kanban … como comando de barra, ou pelo painel. Esses são os caminhos para humanos e automação — os lugares sem um modelo com chamada de ferramentas por trás.

As duas superfícies passam pela mesma camada kanban_db, então as leituras veem uma visão consistente e as escritas não têm como divergir. O resto desta página mostra exemplos de CLI porque são fáceis de copiar e colar, mas todo verbo da CLI tem uma chamada de ferramenta equivalente que o modelo usa.

Este é o formato que cobre as cargas de trabalho que o delegate_task não dá conta:

  • Triagem de pesquisa — pesquisadores em paralelo + analista + redator, com humano no circuito.
  • Operações agendadas — resumos diários recorrentes que montam um diário ao longo de semanas.
  • Gêmeos digitais — assistentes nomeados e persistentes (inbox-triage, ops-review) que acumulam memória com o tempo.
  • Pipelines de engenharia — decompor → implementar em worktrees paralelas → revisar → iterar → PR.
  • Trabalho de frota — um especialista cuidando de N sujeitos (50 contas sociais, 12 serviços monitorados).

Para a justificativa completa de projeto, a análise comparativa com Cline Kanban / Paperclip / NanoClaw / Google Gemini Enterprise e os oito padrões canônicos de colaboração, veja docs/hermes-kanban-v1-spec.pdf no repositório.

Kanban x delegate_task​

Eles parecem iguais; não são a mesma primitiva.

delegate_taskKanban
FormatoChamada RPC (fork → join)Fila de mensagens durável + máquina de estados
PaiBloqueia até o filho retornarDispara e esquece depois do create
Identidade do filhoSubagente anônimoPerfil nomeado com memória persistente
RetomadaNenhuma — falhou, falhouBloquear → desbloquear → rodar de novo; travou → recuperar
Humano no circuitoNão suportadoComentar / desbloquear a qualquer momento
Agentes por tarefaUma chamada = um subagenteN agentes ao longo da vida da tarefa (repetição, revisão, continuação)
Trilha de auditoriaPerdida na compressão de contextoLinhas duráveis em SQLite para sempre
CoordenaçãoHierárquica (chamador → chamado)Entre pares — qualquer perfil lê/escreve qualquer tarefa

A distinção em uma frase: delegate_task é uma chamada de função; o Kanban é uma fila de trabalho em que todo repasse é uma linha que qualquer perfil (ou humano) pode ver e editar.

Use delegate_task quando o agente pai precisa de uma resposta curta de raciocínio antes de continuar, sem humanos envolvidos, e o resultado volta para o contexto do pai.

Use o Kanban quando o trabalho cruza fronteiras entre agentes, precisa sobreviver a reinicializações, pode precisar de entrada humana, pode ser assumido por outro papel, ou precisa ser localizável depois.

Eles convivem: um worker do kanban pode chamar delegate_task internamente durante sua execução.

Conceitos centrais​

  • Quadro (board) — uma fila independente de tarefas com seu próprio banco SQLite, diretório de espaços de trabalho e laço de dispatcher. Uma única instalação pode ter vários quadros (por exemplo, um por projeto, repositório ou domínio); veja Quadros (multiprojeto) mais abaixo. Quem usa um projeto só fica no quadro default e nunca vê a palavra "quadro" fora desta seção da documentação.
  • Tarefa — uma linha com título, corpo opcional, um responsável (nome de perfil), status (triage | todo | ready | running | blocked | review | done | archived), namespace de tenant opcional, chave de idempotência opcional (deduplicação para automação repetida).
  • Vínculo (link) — linha em task_links que registra uma dependência pai → filho. O dispatcher promove todo → ready quando todos os pais estão done.
  • Comentário — o protocolo entre agentes. Agentes e humanos acrescentam comentários; quando um worker é (re)criado, ele lê a thread inteira de comentários como parte do contexto.
  • Espaço de trabalho (workspace) — o diretório em que o worker opera. Três tipos:
    • scratch (padrão) — diretório temporário novo em ~/.hermes/kanban/workspaces/<id>/ (ou ~/.hermes/kanban/boards/<slug>/workspaces/<id>/ em quadros que não são o default). Apagado quando a tarefa termina — o scratch é efêmero por projeto. Arquivos declarados explicitamente por kanban_complete(artifacts=[...]) são copiados para o armazenamento durável de anexos da tarefa antes da limpeza; caminhos de entregáveis já existentes em resumos de conclusão antigos recebem o mesmo tratamento. Os demais arquivos do scratch são removidos. Um artefato de scratch declarado mas ausente mantém a tarefa em andamento, para o worker corrigir o caminho e tentar de novo. Use worktree: ou dir:<path> quando o espaço de trabalho inteiro tiver que continuar disponível. Na primeira vez que um espaço scratch é criado numa instalação, o dispatcher registra um aviso e emite um evento tip_scratch_workspace na tarefa (visível por hermes kanban show <id>).
    • dir:<path> — um diretório compartilhado que já existe (cofre do Obsidian, diretório de operações de e-mail, pasta por conta). Tem que ser um caminho absoluto. Caminhos relativos como dir:../tenants/foo/ são rejeitados no despacho porque resolveriam a partir de qualquer CWD em que o dispatcher estiver, o que é ambíguo e é um vetor de fuga do tipo "delegado confuso". Fora isso o caminho é confiável — é a sua máquina, o seu sistema de arquivos, e o worker roda com o seu uid. Esse é o modelo de ameaça de usuário local confiável; o kanban é de host único por projeto. Preservado na conclusão.
    • worktree — uma worktree do git em .worktrees/<id>/ para tarefas de código. Use worktree:<path> para fixar o caminho exato. O git worktree add do lado do worker cria a worktree, usando --branch quando informado. Preservado na conclusão.
  • Dispatcher — um laço de longa duração que, a cada N segundos (padrão 60): recupera reivindicações vencidas, recupera workers que travaram (PID sumiu mas o TTL ainda não expirou), promove tarefas prontas, reivindica atomicamente e cria os perfis atribuídos. Roda dentro do gateway por padrão (kanban.dispatch_in_gateway: true). Um dispatcher varre todos os quadros por tick; os workers são criados com HERMES_KANBAN_BOARD fixado, para não enxergarem outros quadros. Depois de kanban.failure_limit falhas de criação consecutivas na mesma tarefa (padrão: 2), o dispatcher a bloqueia automaticamente com o último erro como motivo — o que evita ficar batendo em tarefas cujo perfil não existe, cujo espaço de trabalho não monta etc.
  • Tenant — namespace opcional em texto dentro de um quadro. Uma frota de especialistas pode atender vários negócios (--tenant business-a) com isolamento de dados por caminho de espaço de trabalho e por prefixo de chave de memória. Tenants são um filtro leve; os quadros é que são a fronteira dura de isolamento.

Quadros (multiprojeto)​

Os quadros deixam você separar fluxos de trabalho não relacionados — um por projeto, repositório ou domínio — em filas isoladas. Uma instalação nova tem exatamente um quadro chamado default (banco em ~/.hermes/kanban.db, por compatibilidade). Quem só quer um fluxo de trabalho nunca precisa saber de quadros; o recurso é opcional.

O isolamento por quadro é absoluto:

  • Um banco SQLite separado por quadro (~/.hermes/kanban/boards/<slug>/kanban.db).
  • Diretórios workspaces/ e logs/ separados.
  • Os workers criados para uma tarefa veem apenas as tarefas do quadro deles — o dispatcher define HERMES_KANBAN_BOARD no ambiente do processo filho e toda ferramenta kanban_* a que o worker tem acesso lê essa variável.
  • Vincular tarefas entre quadros não é permitido (mantém o schema simples; se você realmente precisar de referências entre projetos, use menções em texto livre e procure pelo id manualmente).

Gerenciar quadros pela CLI​

# Veja o que existe em disco. Instalações novas mostram só "default".
hermes kanban boards list

# Crie um quadro novo.
hermes kanban boards create atm10-server \
--name "ATM10 Server" \
--description "Operações do servidor modado de Minecraft" \
--icon 🎮 \
--switch # opcional: torna este o quadro ativo

# Opere sobre um quadro específico sem trocar o ativo.
hermes kanban --board atm10-server list
hermes kanban --board atm10-server create "Reiniciar o servidor ATM" --assignee ops

# Mude qual quadro é o "atual" para as chamadas seguintes.
hermes kanban boards switch atm10-server
hermes kanban boards show # qual está ativo agora?

# Renomeie o nome de exibição (o slug é imutável — é o nome do diretório).
hermes kanban boards rename atm10-server "ATM10 (Prod)"

# Arquivar (padrão) — move o diretório do quadro para boards/_archived/<slug>-<ts>/.
# Dá para recuperar movendo o diretório de volta.
hermes kanban boards rm atm10-server

# Exclusão definitiva — `rm -rf` no diretório do quadro. Sem recuperação.
hermes kanban boards rm atm10-server --delete

Ordem de resolução do quadro (da maior precedência para a menor):

  1. --board <slug> explícito na chamada de CLI.
  2. Variável de ambiente HERMES_KANBAN_BOARD (definida pelo dispatcher ao criar um worker, para que os workers não vejam outros quadros).
  3. ~/.hermes/kanban/current — o slug persistido por hermes kanban boards switch.
  4. default.

Os slugs são validados: minúsculas alfanuméricas + hifens + sublinhados, de 1 a 64 caracteres, começando por alfanumérico. Entrada em maiúsculas vira minúscula automaticamente. Qualquer outra coisa (barras, espaços, pontos, ..) é rejeitada na camada de CLI, para que truques de travessia de caminho não consigam nomear um quadro.

Gerenciar quadros pelo painel​

hermes dashboard → a aba Kanban mostra um seletor de quadros no topo assim que existir mais de um quadro (ou assim que algum quadro tiver tarefas). Quem usa um quadro só vê apenas um botãozinho + New board; o seletor fica escondido até fazer diferença.

  • Menu de quadros — escolhe o quadro ativo. Sua escolha é salva no localStorage do navegador, então sobrevive a recarregamentos sem mexer no ponteiro current da CLI por baixo de algum terminal que você deixou aberto.
  • + New board — abre um modal pedindo slug, nome de exibição, descrição e ícone. Tem opção de trocar automaticamente para o quadro novo.
  • Settings — abre um modal para editar o nome de exibição, a descrição e o diretório do projeto (default_workdir) do quadro atual. O diretório do projeto é o padrão de espaço de trabalho no nível do quadro que toda tarefa nova herda (repositório git → worktree preservada, diretório comum → diretório preservado); cada tarefa ainda pode sobrescrever isso na criação. Limpar o campo faz as tarefas novas voltarem a usar espaços scratch descartáveis.
  • Archive — só aparece em quadros que não são o default. Pede confirmação e move o diretório do quadro para boards/_archived/.

Todos os endpoints de API do painel aceitam ?board=<slug> para escopo de quadro. O WebSocket de eventos é fixado a um quadro no momento da conexão; trocar de quadro na interface abre um WS novo contra o novo quadro.

Anexos de arquivo​

As tarefas podem carregar anexos — PDFs, imagens, documentos de origem — para que o worker tenha o material de que precisa sem você colar caminhos no corpo e torcer para ele achar.

  • Upload — abra uma tarefa na gaveta do painel e use o botão Upload file da seção Attachments (vários arquivos de uma vez está tudo bem). Cada upload é limitado a 25 MB.
  • Armazenamento — os arquivos ficam em <hermes-home>/kanban/attachments/<task_id>/ no quadro default, ou em <hermes-home>/kanban/boards/<slug>/attachments/<task_id>/ num quadro nomeado. Defina HERMES_KANBAN_ATTACHMENTS_ROOT para fixar um local personalizado.
  • O que o worker vê — quando o dispatcher entrega uma tarefa a um worker, o contexto do worker inclui uma seção Attachments listando o nome de cada arquivo e seu caminho absoluto. O worker tem acesso completo a ferramentas de arquivo e terminal, então lê os anexos direto (read_file, ou ferramentas de shell como pdftotext).
  • Baixar / remover — a gaveta lista cada anexo com um link de download e um controle de remoção (×). Remover um anexo apaga tanto a linha de metadados quanto o arquivo em disco.
Backends de terminal remotos

Os caminhos dos anexos resolvem direto no backend de terminal local, que é o padrão para os workers do Kanban. Se você roda workers num backend remoto (Docker, Modal), monte o diretório attachments/ do quadro dentro do sandbox, para que os caminhos absolutos do contexto do worker sejam alcançáveis.

Início rápido​

Os comandos abaixo são você (humano) montando o quadro e criando tarefas. Depois que uma tarefa é atribuída, o dispatcher cria o perfil atribuído como worker e, dali em diante, o modelo conduz a tarefa por chamadas de ferramenta kanban_*, não por comandos de CLI — veja Como os workers interagem com o quadro.

# 1. Crie o quadro (você)
hermes kanban init

# 2. Suba o gateway (que hospeda o dispatcher embutido)
hermes gateway start

# 3. Crie uma tarefa (você — ou um agente orquestrador, via kanban_create)
hermes kanban create "pesquisar o cenário de investimento em IA" --assignee researcher

# 4. Acompanhe a atividade ao vivo (você)
hermes kanban watch

# 5. Veja o quadro (você)
hermes kanban list
hermes kanban stats

Quando o dispatcher pega t_abcd e cria o perfil researcher, a primeira coisa que o modelo daquele worker faz é chamar kanban_show() para ler a tarefa. Ele não roda hermes kanban show t_abcd.

Dispatcher embutido no gateway (padrão)​

O dispatcher roda dentro do processo do gateway. Nada a instalar, nenhum serviço separado para administrar — se o gateway está no ar, as tarefas prontas são pegas no próximo tick (60 s por padrão).

# config.yaml
kanban:
dispatch_in_gateway: true # padrão
dispatch_interval_seconds: 60 # padrão
review_dispatch: true # padrão: cria o perfil atribuído com a
# skill sdlc-review embutida. Use false
# em quadros de revisão só por humanos.

Sobrescreva a flag de configuração em tempo de execução por HERMES_KANBAN_DISPATCH_IN_GATEWAY=0 para depurar. Vale a supervisão normal do gateway: rode hermes gateway start diretamente, ou configure o gateway como unidade de usuário do systemd (veja a documentação do gateway). Sem um gateway rodando, as tarefas em ready ficam onde estão até algum subir — o hermes kanban create avisa disso na criação.

Rodar hermes kanban daemon como processo separado está descontinuado; use o gateway. Se você realmente não puder rodar o gateway (a política do host sem interface proíbe serviços de longa duração etc.), uma saída de emergência com --force mantém o daemon autônomo antigo vivo por um ciclo de release, mas rodar ao mesmo tempo um dispatcher embutido no gateway E um daemon autônomo contra o mesmo kanban.db provoca corridas de reivindicação e não é suportado.

Criação idempotente (para automação / webhooks)​

# A primeira chamada cria a tarefa. Qualquer chamada seguinte com a mesma chave
# devolve o id da tarefa existente em vez de duplicar.
hermes kanban create "revisão noturna de operações" \
--assignee ops \
--idempotency-key "nightly-ops-$(date -u +%Y-%m-%d)" \
--json

Verbos de CLI em lote​

Todos os verbos de ciclo de vida aceitam vários ids, para você limpar um lote num comando só:

hermes kanban complete t_abc t_def t_hij --result "fechamento do lote"
hermes kanban archive t_abc t_def t_hij
hermes kanban unblock t_abc t_def
hermes kanban block t_abc "falta entrada" --ids t_def t_hij
Onde uma tarefa desbloqueada vai parar

O unblock restaura a fase de origem segura: review para trabalho vindo de revisão cujos pais estão completos, ready para trabalho de implementação cujos pais estão completos, ou todo enquanto algum pai continuar aberto. Uma tarefa em todo guarda a procedência da fase de origem e volta a review ou ready automaticamente quando a trava de dependência libera. O unblock nunca roteia direto para triage.

Se você desbloqueia uma tarefa e depois ela aparece em triage, não foi o unblock que a colocou lá. Foi um rebloqueio pelo mesmo motivo: depois que uma tarefa é bloqueada → desbloqueada → rebloqueada pela mesma causa BLOCK_RECURRENCE_LIMIT vezes (padrão 2), o disjuntor de laço de desbloqueio para de mandá-la de volta para blocked — onde um cron simplesmente ficaria desbloqueando de novo — e a roteia para triage, para uma decisão humana. Isso é uma proteção determinística no banco, não um julgamento do LLM, e o texto do corpo da tarefa não consegue optar por sair dela: o contador de recorrência sobrevive de propósito a cada desbloqueio (só zera num complete bem-sucedido). Para manter uma tarefa desbloqueada no pool de trabalho, resolva por que ela rebloqueia (pai inacabado, entrada faltando, capacidade ausente) antes de desbloquear, ou aumente o BLOCK_RECURRENCE_LIMIT se o laço for esperado.

Como os workers interagem com o quadro​

Os workers não chamam hermes kanban no shell. Quando o dispatcher cria um worker, ele define HERMES_KANBAN_TASK=t_abcd no ambiente do processo filho, e essa variável liga um conjunto de ferramentas kanban dedicado no schema do modelo. O mesmo conjunto também fica disponível para perfis orquestradores que habilitam kanban na configuração de toolsets. Essas ferramentas leem e alteram o quadro direto pela camada Python kanban_db, igual à CLI. Um worker em execução chama essas ferramentas como qualquer outra; ele nunca vê nem precisa da CLI hermes kanban.

FerramentaPara que serveParâmetros obrigatórios
kanban_showLer a tarefa atual (título, corpo, tentativas anteriores, repasses dos pais, comentários, o worker_context completo já formatado). Usa por padrão o id de tarefa do ambiente.—
kanban_listListar resumos de tarefas com filtros de assignee, status, tenant, visibilidade de arquivadas e limite. Pensada para orquestradores descobrirem o trabalho do quadro.—
kanban_completeEncerrar com repasse estruturado summary + metadata.ao menos um entre summary / result
kanban_request_reviewIniciar a revisão no mesmo cartão, com um summary durável, metadata opcional e perfil revisor opcional. A tarefa vai para review; isso não é um bloqueio.summary
kanban_request_changesVeredito do revisor a partir de uma revisão ativa. Fecha aquela execução, reaplica a trava dos pais e roteia a tarefa de volta ao implementador original, sem contabilizar laço de bloqueio.reason
kanban_blockParar o trabalho e rotear pelo motivo: kind=dependency (espera em todo, retoma sozinha), needs_input/capability/transient (aparece para um humano). Rebloqueios repetidos do mesmo tipo escalam sozinhos para triage.reason
kanban_heartbeatSinalizar que está vivo durante operações longas. Puro efeito colateral.—
kanban_commentAcrescentar uma nota durável à thread da tarefa.task_id, body
kanban_attachAnexar um arquivo à tarefa passando os bytes inline (base64); guardado no diretório de anexos da tarefa (limite de 25 MB).bytes do arquivo + nome
kanban_attach_urlAnexar um arquivo à tarefa por URL.url
kanban_attachmentsListar os anexos de uma tarefa.—
kanban_create(Orquestradores) abrir em leque tarefas filhas com um assignee, mais parents, skills etc. opcionais.title, assignee
kanban_link(Orquestradores) acrescentar uma aresta de dependência parent_id → child_id depois do fato.parent_id, child_id
kanban_unblock(Orquestradores) restaurar uma tarefa bloqueada à fase de origem (review ou ready), ou todo enquanto um pai seguir aberto.task_id

Um turno típico de worker é assim:

# Chamadas de ferramenta do modelo, em ordem:
kanban_show() # sem argumentos — usa HERMES_KANBAN_TASK
# (o modelo lê o worker_context devolvido e faz o trabalho pelas ferramentas de terminal/arquivo)
kanban_heartbeat(note="na metade — 4 de 8 arquivos transformados")
# (mais trabalho)
kanban_complete(
summary="migrei limiter.py para token bucket; adicionei 14 testes, todos passam",
metadata={"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14},
)

Um worker orquestrador abre em leque, em vez disso:

kanban_show()
kanban_create(
title="pesquisar investimento em ICP 2024-2026",
assignee="researcher-a",
body="foco em seed + série A, América do Norte, adjacente a IA",
)
# → devolve {"task_id": "t_r1", ...}
kanban_create(title="pesquisar investimento em ICP — recorte UE", assignee="researcher-b", body="…")
# → devolve {"task_id": "t_r2", ...}
kanban_create(
title="sintetizar os achados num briefing de lançamento",
assignee="writer",
parents=["t_r1", "t_r2"], # promove para ready quando as duas terminarem
body="uma página, 300 palavras, tom neutro",
)
kanban_complete(summary="decompus em 2 tarefas de pesquisa + 1 de redação; dependências vinculadas")

As ferramentas "(Orquestradores)" — kanban_list, kanban_create, kanban_link, kanban_unblock e kanban_comment em tarefas de terceiros — estão disponíveis pelo mesmo conjunto; a convenção (codificada na orientação de kanban injetada automaticamente) é que perfis worker não abrem leque nem roteiam trabalho alheio, e perfis orquestradores não executam trabalho de implementação. Os workers criados pelo dispatcher continuam limitados à própria tarefa nas operações destrutivas de ciclo de vida e não conseguem alterar tarefas alheias.

Por que ferramentas em vez de chamar hermes kanban no shell​

Três motivos:

  1. Portabilidade de backend. Workers cuja ferramenta de terminal aponta para um backend remoto (Docker / Modal / Singularity / SSH) rodariam hermes kanban complete dentro do contêiner, onde o hermes não está instalado e o ~/.hermes/kanban.db não está montado. As ferramentas de kanban rodam no próprio processo Python do agente e sempre alcançam o ~/.hermes/kanban.db, seja qual for o backend de terminal.
  2. Sem fragilidade de aspas do shell. Passar --metadata '{"files": [...]}' por shlex + argparse é uma armadilha latente. Argumentos estruturados de ferramenta pulam isso inteiro.
  3. Erros melhores. Resultados de ferramenta são JSON estruturado, sobre o qual o modelo consegue raciocinar, e não strings de stderr que ele precisa interpretar.

Zero pegada de schema em sessões normais. Uma sessão comum de hermes chat tem zero ferramentas kanban_* no schema, a menos que o perfil ativo habilite explicitamente o conjunto kanban para trabalho de orquestração. Os workers de tarefa criados pelo dispatcher recebem as ferramentas com escopo de tarefa porque HERMES_KANBAN_TASK está definido; perfis orquestradores recebem a superfície mais ampla de roteamento por configuração. Sem inchaço de ferramentas para quem nunca encosta no kanban.

A orientação de kanban injetada automaticamente ensina o modelo qual ferramenta chamar, quando e em que ordem.

O kanban_complete(summary=..., metadata={...}) é flexível de propósito: o resumo é o fechamento legível por humanos, e metadata é o repasse legível por máquina que agentes seguintes, revisores ou painéis podem reaproveitar sem raspar prosa.

Para tarefas de engenharia e de revisão, prefira este formato opcional de metadados:

{
"changed_files": ["caminho/do/arquivo.py"],
"verification": ["pytest tests/hermes_cli/test_kanban_db.py -q"],
"dependencies": ["id da tarefa pai ou issue externa, se houver"],
"blocked_reason": null,
"retry_notes": "o que falhou antes, se isto foi uma repetição",
"residual_risk": ["o que não foi testado ou ainda precisa de revisão humana"]
}

Essas chaves são uma convenção, não uma exigência de schema. A propriedade útil é que todo worker deixa evidência suficiente para o próximo leitor responder rápido a quatro perguntas:

  1. O que mudou?
  2. Como foi verificado?
  3. O que consegue desbloquear ou repetir isso se falhar?
  4. Que risco ainda foi deliberadamente deixado em aberto?

Mantenha segredos, logs crus, tokens, material de OAuth e transcrições alheias fora de metadata. Guarde ponteiros e resumos no lugar. Se uma tarefa não tem arquivos nem testes, diga isso explicitamente no summary e use metadata para a evidência que existe, como URLs de origem, ids de issue ou passos de revisão manual.

O ciclo de vida do worker​

Todo perfil que trabalha em tarefas do kanban ganha automaticamente o ciclo de vida de worker — ele é injetado no prompt de sistema do worker na criação (o bloco KANBAN_GUIDANCE), então não há nada para instalar ou configurar. Ele ensina ao worker o ciclo completo em chamadas de ferramenta, não em comandos de CLI:

  1. Na criação, chame kanban_show() para ler título + corpo + repasses dos pais + tentativas anteriores + a thread completa de comentários.
  2. Faça cd $HERMES_KANBAN_WORKSPACE (pela ferramenta de terminal) e trabalhe ali.
  3. Chame kanban_heartbeat(note="...") a cada poucos minutos durante operações longas. Se o seu trabalho puder passar de 1 hora, chame kanban_heartbeat pelo menos uma vez por hora — o dispatcher recupera tarefas que estão rodando além de kanban.dispatch_stale_timeout_seconds (padrão 4 h) sem heartbeat na última hora, presumindo que o worker travou sem limpar nada. Uma recuperação é inofensiva (a tarefa volta para ready e é redespachada, sem incrementar o contador de falhas), mas você perde o progresso da execução atual.
  4. Encerre com kanban_complete(summary="...", metadata={...}), ou com kanban_block(reason="...") se travar.

Essa chamada final de kanban_complete / kanban_block faz parte do protocolo do worker. Se o processo do worker sai com status 0 enquanto a tarefa ainda está running, o dispatcher trata isso como violação de protocolo e emite um evento protocol_violation.

Prevenção do lado do agente: antes de o worker sair, o Hermes injeta até dois cutucões sintéticos quando detecta que o modelo está prestes a parar sem uma chamada terminal de ferramenta do quadro. Isso pega o caso comum em que o modelo narra o próximo passo ("Vou escrever o relatório") e para com finish_reason=stop. O cutucão lembra o modelo de chamar kanban_complete ou kanban_block imediatamente. Essa proteção só está ativa para workers criados pelo dispatcher (HERMES_KANBAN_TASK definido) e pode ser desligada com HERMES_KANBAN_STOP_NUDGE=0.

Recuperação do lado do dispatcher: se os cutucões acabam ou o worker trava antes de chegar neles, o dispatcher dá à violação uma repetição limitada (até _PROTOCOL_VIOLATION_FAILURE_LIMIT violações consecutivas, padrão 3) antes de bloquear a tarefa automaticamente, em vez de recriá-la no mesmo laço. O orçamento conta só violações de protocolo consecutivas com saída limpa — reenfileiramentos por limite de taxa no meio são neutros, e qualquer outro tipo de falha zera a sequência — e um max_retries por tarefa sobrescreve o limite. Isso costuma significar que o modelo escreveu uma resposta em texto puro e saiu sem usar a superfície de ferramentas do Kanban.

O ciclo de vida mais os detalhes de referência que sustentam tudo (tipos de espaço de trabalho, artifacts de entregáveis, reivindicar cartões criados) vêm nesse bloco de prompt de sistema, então todo worker os tem, independentemente do perfil sob o qual roda — sem precisar configurar skill por perfil.

Fixar skills extras numa tarefa específica​

Às vezes uma única tarefa precisa de contexto especialista que o perfil responsável não carrega por padrão — um trabalho de tradução que precisa da skill translation, uma tarefa de revisão que precisa de github-code-review, uma auditoria de segurança que precisa de security-pr-audit. Em vez de editar o perfil do responsável toda vez, anexe as skills direto na tarefa.

A partir de um agente orquestrador (o caso usual — um agente roteando trabalho para outro), use o array skills da ferramenta kanban_create:

kanban_create(
title="traduzir o README para japonês",
assignee="linguist",
skills=["translation"],
)

kanban_create(
title="auditar o fluxo de autenticação",
assignee="reviewer",
skills=["security-pr-audit", "github-code-review"],
)

A partir de um humano (CLI / comando de barra), repita --skill para cada uma:

hermes kanban create "traduzir o README para japonês" \
--assignee linguist \
--skill translation

hermes kanban create "auditar o fluxo de autenticação" \
--assignee reviewer \
--skill security-pr-audit \
--skill github-code-review

Pelo painel, digite as skills separadas por vírgula no campo skills do diálogo de criação de tarefa.

O dispatcher emite uma flag --skills <name> por skill listada, então o worker nasce com todas carregadas por cima da orientação de kanban injetada automaticamente. Os nomes das skills têm que bater com skills realmente instaladas no perfil do responsável (rode hermes skills list para ver o que está disponível); não existe instalação em tempo de execução.

Sobrescrita de modelo por tarefa​

Fixe o worker de uma tarefa num modelo específico (e opcionalmente num provedor), independentemente do padrão do perfil responsável:

# Na criação
hermes kanban create "refatoração difícil" --assignee coder \
--model claude-opus-4.6 --provider anthropic

# Ou depois — vale a partir do próximo despacho
hermes kanban set-model t_abcd claude-opus-4.6 --provider anthropic
hermes kanban set-model t_abcd none # remove a sobrescrita

O dispatcher cria o worker com o modelo fixado (--provider <name> é passado quando definido; --provider exige um modelo). O menu de modelo por tarefa do painel alimenta o mesmo campo model_override. Sem sobrescrita, o worker usa o modelo configurado no perfil dele.

Estratégia de custo: orquestrador de fronteira, workers baratos​

As configurações por perfil do Kanban tornam natural a divisão de custo entre planejador e worker. Decompor um projeto em cartões bem delimitados exige julgamento de modelo de fronteira; executar um cartão que já traz objetivo claro, contexto e evidência de repasse normalmente não exige — e são os workers que gastam a esmagadora maioria dos tokens, então o custo mora no modelo dos workers. Rode o perfil de orquestrador/dispatcher num modelo de fronteira e aponte os perfis worker para modelos baratos. Cada perfil tem seu próprio config.yaml em ~/.hermes/profiles/<name>/, e o dispatcher injeta o HERMES_HOME com escopo de perfil ao criar hermes -p <assignee>, então cada worker lê as configurações de modelo do próprio perfil:

# ~/.hermes/config.yaml (perfil orquestrador / dispatcher)
model:
default: "seu-modelo-de-fronteira"

# ~/.hermes/profiles/coder/config.yaml (perfil worker)
model:
default: "seu-modelo-barato"

# ~/.hermes/profiles/researcher/config.yaml (outro perfil worker)
model:
default: "seu-modelo-barato"

Para aquele cartão eventual sensível a qualidade, fixe só aquela tarefa de volta num modelo mais forte com a sobrescrita de modelo por tarefa (--model/--provider na criação, hermes kanban set-model depois, ou o menu de modelo do painel) — sem editar perfil nenhum.

Hooks de plugin no ciclo de vida​

As transições do quadro disparam hooks de plugin: kanban_task_claimed, kanban_task_completed e kanban_task_blocked, cada um carregando task_id e profile_name. Os hooks disparam depois que a mudança no banco do quadro é confirmada, então os callbacks sempre veem estado durável. Repare na divisão de processos: kanban_task_claimed dispara no processo do dispatcher, enquanto kanban_task_completed/kanban_task_blocked disparam no processo do worker — registre o hook no perfil do dispatcher para observar todas as transições de forma centralizada.

def register(ctx):
def on_blocked(task_id=None, profile_name=None, **kw):
ctx.dispatch_tool("terminal", {"command": f"notify-send 'kanban bloqueado: {task_id}'"})
ctx.register_hook("kanban_task_blocked", on_blocked)

Cartões em modo meta (--goal)​

Por padrão cada worker tem uma tentativa no cartão — faz o trabalho, chama kanban_complete/kanban_block, sai. Passe --goal (CLI) ou goal_mode=True (na ferramenta kanban_create / no painel) para, em vez disso, rodar aquele worker num laço de meta, o mesmo motor no estilo Ralph que está por trás do comando de barra /goal: depois de cada turno, um juiz auxiliar confere a saída do worker contra o título + corpo do cartão (tratados como critérios de aceite) e, se o trabalho não estiver pronto — e ainda houver orçamento de turnos —, o worker continua na mesma sessão até o juiz concordar, o worker encerrar a tarefa sozinho, ou o orçamento acabar (o que bloqueia o cartão para revisão humana, em vez de sair em silêncio).

hermes kanban create "Traduzir o site de documentação para o francês" \
--body "Aceite: toda página traduzida, nada de inglês sobrando, links intactos." \
--assignee linguist \
--goal \
--goal-max-turns 15 # opcional; padrão 20

Use isso em cartões abertos, de vários passos, ou do tipo "continue até que X seja verdade". Pule em trabalho barato de uma tacada só — o custo do juiz por turno não compensa, e a repetição/disjuntor que o dispatcher já tem cuida de falhas transitórias do worker. O juiz é tão bom quanto o texto da sua meta, então escreva o corpo como critérios de aceite explícitos.

Cartões em modo meta pegam emprestado o motor do /goal — eles não se conectam a ele

O --goal roda o laço de continuação dentro da sessão do worker daquele cartão. Ele compartilha o motor com o comando de barra /goal, não o estado: definir um /goal numa sessão de chat nunca cria, reivindica ou move um cartão do kanban, e o laço de um cartão em modo meta é invisível para o /goal status de qualquer sessão de chat. Se você quer que esta conversa continue iterando, use /goal; se você quer trabalho no quadro, crie um cartão.

Como o orquestrador se comporta​

Um orquestrador bem-comportado não faz o trabalho ele mesmo. Ele decompõe o objetivo do usuário em tarefas, vincula essas tarefas, atribui cada uma a um dos perfis que você configurou, e sai de cena. A orientação de orquestrador — regras anti-tentação, um prompt de Passo 0 para descobrir perfis (o dispatcher falha em silêncio com nomes de responsável desconhecidos, então o orquestrador precisa ancorar cada cartão em perfis que existam de fato na sua máquina) e um manual de decomposição baseado em kanban_create / kanban_link / kanban_comment — é injetada automaticamente no prompt de sistema do worker; não há nada para instalar.

Um turno canônico de orquestrador (dois pesquisadores em paralelo repassando para um redator):

# Objetivo do usuário: "escreva um post de lançamento sobre o cenário de investimento em ICP"
kanban_create(title="pesquisar investimento em ICP, recorte AN", assignee="researcher-a", body="…") # → t_r1
kanban_create(title="pesquisar investimento em ICP, recorte UE", assignee="researcher-b", body="…") # → t_r2
kanban_create(
title="sintetizar a pesquisa de investimento em ICP num rascunho de post de lançamento",
assignee="writer",
parents=["t_r1", "t_r2"], # promovida para 'ready' quando os dois pesquisadores terminarem
body="uma página, tom neutro, citar fontes no texto",
) # → t_w1
# Opcional: acrescentar dependências transversais descobertas depois, sem recriar tarefas
kanban_link(parent_id="t_r1", child_id="t_followup")
kanban_complete(
summary="decompus em 2 tarefas de pesquisa em paralelo → 1 tarefa de síntese; o redator começa quando os dois pesquisadores terminarem",
)

A orientação de orquestrador vem no prompt de sistema do worker automaticamente — não há nada para instalar nem sincronizar por perfil.

Decida antes de abrir o leque. As decisões de projeto são do orquestrador, não dos workers. Se dois cartões em paralelo teriam cada um que escolher a mesma coisa — um esquema de nomes, um schema, um formato de arquivo, o formato de uma API —, o orquestrador decide uma vez e carimba a decisão no corpo dos dois cartões. Os workers não conseguem ver os cartões irmãos, então o corpo de cada cartão filho precisa carregar toda decisão da qual ele depende. Exemplo: para os cartões paralelos "construir o exportador" e "construir o importador", não deixe cada worker inventar seu próprio formato de arquivo — escolha um de saída (digamos, JSON delimitado por quebra de linha com um campo version) e escreva isso nos dois corpos, ou as duas metades nunca vão conversar de ida e volta.

Para o melhor resultado, combine isso com um perfil cujos toolsets estejam restritos a operações de quadro (kanban, gateway, memory), para que o orquestrador literalmente não consiga executar tarefas de implementação nem se tentar.

Painel (GUI)​

A CLI e o comando de barra /kanban bastam para tocar o quadro sem interface, mas um quadro visual costuma ser a interface certa para o humano no circuito: triagem, supervisão entre perfis, leitura de threads de comentários e arrastar cartões entre colunas. O Hermes entrega isso como um plugin embutido do painel em plugins/kanban/ — não é recurso do núcleo, não é serviço separado — seguindo o modelo descrito em Estender o painel.

Abra com:

hermes kanban init # uma vez: cria o kanban.db se ainda não existir
hermes dashboard # a aba "Kanban" aparece na navegação, depois de "Skills"

O que o plugin te dá​

  • Uma aba Kanban mostrando uma coluna por status: triage, todo, ready, running, blocked, done (mais archived quando a chave está ligada).
    • triage é a coluna de estacionamento para ideias cruas. Por padrão (kanban.auto_decompose: true), o dispatcher roda o decompositor automaticamente nas tarefas que caem ali. O decompositor embutido usa o caminho de modelo auxiliary.kanban_decomposer, lê a sua lista de perfis (com descrições) e abre a tarefa em leque num pequeno grafo de tarefas filhas roteadas aos especialistas mais adequados. A tarefa original continua viva como pai de cada filha, para que o responsável dela (kanban.orchestrator_profile, ou o perfil padrão ativo quando não definido) acorde para julgar a conclusão quando tudo terminar. Alterne a pílula Orchestration: Auto/Manual no topo da página (esmeralda = Auto, cinza apagado = Manual), ou edite o config.yaml direto. Os dois modos convivem com o hermes kanban specify — que continua disponível como reescrita de especificação de uma tarefa só, quando você não quer leque.
  • Os cartões mostram id da tarefa, título, selo de prioridade, etiqueta de tenant, perfil atribuído, contagem de comentários/vínculos, uma pílula de progresso (N/M filhas concluídas quando a tarefa tem dependentes) e "criada há N". Uma caixa de seleção por cartão habilita seleção múltipla.
  • Faixas por perfil dentro de Running — uma caixa de seleção na barra de ferramentas ativa o subagrupamento da coluna Running por responsável.
  • Atualizações ao vivo por WebSocket — o plugin acompanha a tabela append-only task_events num intervalo curto de sondagem; o quadro reflete as mudanças no instante em que qualquer perfil (CLI, gateway ou outra aba do painel) age. As recargas são amortecidas, então uma rajada de eventos dispara uma única nova busca.
  • Arrastar e soltar cartões entre colunas para mudar o status. O soltar envia PATCH /api/plugins/kanban/tasks/:id, que passa pelo mesmo código kanban_db que a CLI usa — as três superfícies nunca divergem. Movimentos para status destrutivos (done, archived, blocked) pedem confirmação. Dispositivos de toque usam um fallback baseado em ponteiro, então o quadro é utilizável de um tablet.
  • Diálogo de criação de tarefa — clique em + no cabeçalho de qualquer coluna para abrir um modal com campos rotulados: título, responsável, prioridade, skills, tipo/caminho de espaço de trabalho (semeado a partir do diretório de projeto do quadro; sobrescrevível por tarefa), modo meta e (opcionalmente) uma tarefa pai, num menu com todas as tarefas existentes. Pressione Enter para criar a tarefa, Shift+Enter para inserir uma quebra de linha no campo de título, ou Escape para cancelar. Criar a partir da coluna Triage estaciona a nova tarefa em triagem automaticamente.
  • Seleção múltipla com ações em lote — shift/ctrl-clique num cartão ou marque a caixa dele para incluí-lo na seleção. Uma barra de ações em lote aparece no topo, com transições de status em lote, arquivar e reatribuir (por menu de perfil, ou "(unassign)"). Lotes destrutivos pedem confirmação antes. Falhas parciais por id são reportadas sem abortar o resto.
  • Clique num cartão (sem shift/ctrl) para abrir uma gaveta lateral (Escape ou clique fora fecha) com:
    • Título editável — clique no cabeçalho para renomear.
    • Responsável / prioridade editáveis — clique na linha de metadados para reescrever.
    • Descrição editável — renderizada como markdown por padrão (títulos, negrito, itálico, código inline, blocos cercados, links http(s) / mailto:, listas com marcadores), com um botão de "edit" que troca por uma área de texto. A renderização de markdown é um renderizador minúsculo e à prova de XSS — toda substituição roda sobre entrada com HTML escapado, só links http(s) / mailto: passam, e target="_blank" + rel="noopener noreferrer" são sempre definidos.
    • Editor de dependências — lista de fichas de pais e filhas, cada uma com um × para desvincular, mais menus com todas as outras tarefas para adicionar um novo pai ou filho. Tentativas de ciclo são rejeitadas no servidor, com mensagem clara.
    • Linha de ações de status (→ triage / → ready / → running / block / unblock / complete / archive) com confirmação nas transições destrutivas. Para cartões na coluna Triage, a linha também expõe duas ações movidas a LLM: ⚗ Decompose abre a tarefa em leque num grafo de tarefas filhas roteadas a perfis especialistas por descrição, e ✨ Specify faz uma reescrita de especificação de uma tarefa só. O Decompose recai numa promoção no estilo specify quando o LLM decide que a tarefa não ganha nada com o leque, então é um superconjunto estrito. Os dois são alcançáveis pela CLI (hermes kanban decompose <id> / specify <id> / --all), por qualquer plataforma do gateway (/kanban decompose <id>) e programaticamente por POST /api/plugins/kanban/tasks/:id/decompose e …/specify. Configure os modelos em auxiliary.kanban_decomposer e auxiliary.triage_specifier no config.yaml.
    • Seção de resultado (também renderizada como markdown), thread de comentários com Enter para enviar, e os últimos 20 eventos.
  • Filtros da barra de ferramentas — busca em texto livre, menu de tenant (que assume o dashboard.kanban.default_tenant do config.yaml), menu de responsável, chave "show archived", chave "lanes by profile" e um botão Nudge dispatcher, para você não ter que esperar o próximo tick de 60 s.

Visualmente o alvo é o layout familiar do Linear / Fusion: tema escuro, cabeçalhos de coluna com contagens, pontinhos coloridos de status, fichas em pílula para prioridade e tenant. O plugin lê apenas as variáveis CSS de tema (--color-*, --radius, --font-mono, ...), então ele muda de pele sozinho conforme o tema de painel ativo.

Orquestração Auto x Manual​

O quadro kanban tem duas maneiras de lidar com uma tarefa que você joga na coluna Triage:

Auto (padrão) — kanban.auto_decompose: true. O dispatcher embutido no gateway roda o decompositor a cada tick, limitado por kanban.auto_decompose_per_tick (padrão 3 tarefas por tick), para que uma carga em massa de tarefas de triagem não estoure o gasto com o LLM auxiliar. O decompositor usa o prompt de decomposição embutido mais o caminho de modelo auxiliary.kanban_decomposer, lê os perfis instalados + suas descrições e pede ao LLM um grafo de tarefas em JSON: quais tarefas criar, para quem vão e quais dependem de quais. A tarefa original de triagem vira pai de cada folha do grafo, então ela fica viva até o grafo inteiro terminar — e aí volta para ready, para que o responsável dela (kanban.orchestrator_profile, ou o perfil padrão ativo quando não definido) possa julgar a conclusão e acrescentar mais tarefas se o trabalho não estiver pronto. Esse é o fluxo "solta uma frase e vai embora".

Manual — kanban.auto_decompose: false. As tarefas de triagem ficam em triagem até você agir. Clique no botão ⚗ Decompose de um cartão, rode hermes kanban decompose <id> (ou --all), ou use /kanban decompose <id> a partir de um chat. Isso corresponde ao comportamento do quadro antes do decompositor, útil quando você quer controle total sobre o que roda e quando.

Fronteira importante: o modo manual desliga apenas o decompositor embutido de Triage. Ele não impede um perfil de chamar kanban_create, e não desliga os despertares da sessão criadora. Com kanban.auto_subscribe_on_create: true, o evento terminal de uma tarefa retoma o agente que a originou com um turno sintético de status, para que ele possa inspecionar o repasse e decidir se há trabalho de continuação genuinamente novo. Use auto_subscribe_on_create: false quando a conclusão da tarefa deve ficar passiva. Para procedência, as filhas do decompositor embutido levam created_by=auto-decomposer; tarefas criadas por um perfil retomado levam o nome daquele perfil.

Alterne entre os dois modos pela pílula Orchestration: Auto/Manual no topo da página do kanban (esmeralda = Auto, cinza apagado = Manual), ou editando o config.yaml direto. Os dois modos convivem com o hermes kanban specify — que continua disponível como reescrita de especificação de uma tarefa só, quando você não quer leque.

As decisões de roteamento do decompositor dependem das descrições de perfil, que são uma primitiva de rotulagem por perfil definida com hermes profile create --description "...", hermes profile describe <name> --text "...", hermes profile describe <name> --auto (o LLM gera a partir das skills instaladas + modelo do perfil), ou pelo editor por perfil do painel, no painel expandido de Orchestration settings. Perfis sem descrição ainda aparecem na lista — são roteáveis por nome, só que com menos precisão. O decompositor NUNCA entrega uma tarefa filha com assignee=None: quando o LLM escolhe um perfil desconhecido, a filha é roteada para kanban.default_assignee (ou para o perfil padrão ativo, se ele não estiver definido).

O kanban.orchestrator_profile não carrega o prompt, as skills nem a lógica personalizada daquele perfil dentro da chamada de decomposição. Ele controla quem é dono da tarefa raiz/de orquestração depois do leque. Para mudar o modelo/provedor do decompositor, configure auxiliary.kanban_decomposer. Para usar a lógica personalizada de divisão de tarefas de um perfil em vez do decompositor embutido, mude para o modo Manual e faça aquele perfil criar ou decompor tarefas explicitamente.

Chaves de configuração (todas sob kanban: no ~/.hermes/config.yaml):

ChavePadrãoPara que serve
auto_decomposetrueO dispatcher roda o decompositor embutido nas tarefas de Triage a cada tick. Não interfere em chamadas de kanban_create feitas por perfis nem nos turnos de despertar do criador.
auto_decompose_per_tick3Teto de decomposições por tick do dispatcher. O excedente fica para o tick seguinte.
orchestrator_profile""Perfil atribuído à tarefa raiz/de orquestração depois da decomposição. Vazio = cai no perfil padrão ativo.
default_assignee""Onde uma tarefa filha vai parar quando o LLM escolhe um perfil desconhecido. Vazio = cai no padrão ativo.
auto_subscribe_on_createtrueQuando o kanban_create roda dentro de uma sessão persistente de gateway/TUI, os eventos terminais retomam o agente de origem com um turno sintético de status. Use false para conclusão passiva ou para exigir chamadas explícitas de kanban_notify-subscribe. Independente de auto_decompose.
done_sub_retention_days30As assinaturas de notificação sobrevivem ao done (é seguro reabrir) e são removidas no archived. O GC do notificador limpa assinaturas cuja tarefa está em done sem novos eventos há esse número de dias, limitando o crescimento da tabela em quadros que nunca arquivam. 0 desliga a varredura.

E as duas vagas de LLM auxiliar:

ChavePara que serve
auxiliary.kanban_decomposerModelo que produz o grafo de tarefas (chamado pelo Decompose). Defina provider/model para sobrescrever o modelo de chat principal.
auxiliary.profile_describerModelo que gera descrições de perfil automaticamente (chamado por hermes profile describe --auto).

Arquitetura​

A GUI é estritamente uma camada de leitura pelo banco + escrita por kanban_db, sem lógica de domínio própria:

┌────────────────────────┐ WebSocket (acompanha task_events)
│ SPA React (plugin) │ ◀──────────────────────────────────┐
│ arrastar-e-soltar │ │
└──────────┬─────────────┘ │
│ REST por fetchJSON │
▼ │
┌────────────────────────┐ escritas chamam kanban_db.* │
│ roteador FastAPI │ direto — o mesmo caminho de │
│ plugins/kanban/ │ código dos verbos /kanban │
│ dashboard/plugin_api.py │
└──────────┬─────────────┘ │
│ │
▼ │
┌────────────────────────┐ │
│ ~/.hermes/kanban.db │ ───── acrescenta task_events ──────┘
│ (WAL, compartilhado) │
└────────────────────────┘

Superfície REST​

Todas as rotas ficam sob /api/plugins/kanban/ e são protegidas pelo token de sessão efêmero do painel:

MétodoCaminhoPara que serve
GET/board?tenant=<name>&include_archived=…Quadro completo agrupado por coluna de status, mais tenants + responsáveis para os menus de filtro
GET/tasks/:idTarefa + comentários + eventos + vínculos
POST/tasksCriar (envolve kanban_db.create_task, aceita triage: bool e parents: [id, …])
PATCH/tasks/:idStatus / responsável / prioridade / título / corpo / resultado
POST/tasks/bulkAplica o mesmo patch (status / arquivar / responsável / prioridade) a todos os ids em ids. Falhas por id são reportadas sem abortar as irmãs
POST/tasks/:id/commentsAcrescenta um comentário
POST/tasks/:id/specifyRoda o especificador de triagem — um LLM auxiliar detalha o corpo da tarefa e a promove de triage para todo. Devolve {ok, task_id, reason, new_title}; ok=false com um motivo legível para "não está em triagem" / sem cliente auxiliar / erro do LLM é um 200, não um 4xx
POST/tasks/:id/decomposeRoda o decompositor do kanban — um LLM auxiliar produz um grafo de tarefas e o helper cria as filhas atomicamente + vincula a raiz + vira triage → todo. Devolve {ok, task_id, reason, fanout, child_ids, new_title}. Mesma convenção de 200 em erro de LLM do /specify.
GET/profilesLista os perfis instalados com suas descrições (consumida pelo editor de descrição de perfil do painel e pelo seletor de orquestrador).
PATCH/profiles/:nameDefine ou limpa a descrição de um perfil (escrita pelo usuário — description_auto: false). Devolve {ok, profile, description}.
POST/profiles/:name/describe-autoGera uma descrição para um perfil via auxiliary.profile_describer. Persiste com description_auto: true, para o painel poder exibir um selo de "revisar".
GET/orchestrationLê as configurações de orquestração do kanban (orchestrator_profile, default_assignee, auto_decompose) mais os valores efetivos já resolvidos depois dos fallbacks.
PUT/orchestrationAtualiza uma ou mais das três chaves de orquestração no config.yaml. Valida que nomes de perfil não vazios existem de fato.
POST/linksAdiciona uma dependência (parent_id → child_id)
DELETE/links?parent_id=…&child_id=…Remove uma dependência
POST/dispatch?max=…&dry_run=…Cutuca o dispatcher — pula a espera de 60 s
GET/configLê as preferências de dashboard.kanban do config.yaml — default_tenant, lane_by_profile, include_archived_by_default, render_markdown
WS/events?since=<event_id>Fluxo ao vivo das linhas de task_events

Cada handler é um invólucro fino — o plugin tem cerca de 700 linhas de Python (roteador + acompanhamento do WebSocket + agrupador de lotes + leitor de configuração) e não acrescenta lógica de negócio nova. Um helper minúsculo _conn() inicializa o kanban.db automaticamente em toda leitura e escrita, então uma instalação nova funciona seja qual for a ordem: painel aberto primeiro, API REST chamada direto, ou hermes kanban init rodado antes.

Configuração do painel​

Qualquer uma destas chaves sob dashboard.kanban no ~/.hermes/config.yaml muda os padrões da aba — o plugin as lê no carregamento, via GET /config:

dashboard:
kanban:
default_tenant: acme # pré-seleciona o filtro de tenant
lane_by_profile: true # padrão da chave "lanes by profile"
include_archived_by_default: false
render_markdown: true # use false para renderizar em <pre> puro

Cada chave é opcional e cai no padrão mostrado.

Modelo de segurança​

O middleware de autenticação HTTP do painel pula explicitamente /api/plugins/ — as rotas de plugin não têm autenticação por projeto, porque o painel escuta em localhost por padrão. Isso significa que a superfície REST do kanban é alcançável por qualquer processo da máquina.

O WebSocket dá um passo a mais: exige o token de sessão efêmero do painel como parâmetro de consulta ?token=… (navegadores não conseguem definir Authorization numa requisição de upgrade), seguindo o mesmo padrão da ponte PTY do navegador.

Se você roda hermes dashboard --host 0.0.0.0, toda rota de plugin — kanban incluído — passa a ser alcançável pela rede. Não faça isso numa máquina compartilhada. O quadro contém corpos de tarefa, comentários e caminhos de espaço de trabalho; um atacante que alcance essas rotas ganha acesso de leitura a toda a sua superfície de colaboração e ainda pode criar / reatribuir / arquivar tarefas.

As tarefas em ~/.hermes/kanban.db são agnósticas de perfil de propósito (essa é a primitiva de coordenação). Se você abre o painel com hermes -p <profile> dashboard, o quadro ainda mostra tarefas criadas por qualquer outro perfil da máquina. O mesmo usuário é dono de todos os perfis, mas vale saber disso se várias personas coexistem.

Atualizações ao vivo​

A task_events é uma tabela SQLite append-only com um id monotônico. O endpoint de WebSocket guarda o último id de evento visto por cliente e empurra as linhas novas conforme elas chegam. Quando chega uma rajada de eventos, o frontend recarrega o endpoint (bem barato) do quadro — mais simples e mais correto do que tentar corrigir o estado local a partir de cada tipo de evento. O modo WAL faz o laço de leitura nunca bloquear as transações de reivindicação BEGIN IMMEDIATE do dispatcher.

Como estender​

O plugin usa o contrato padrão de plugin de painel do Hermes — veja Estender o painel para a referência completa de manifesto, slots de shell, slots com escopo de página e o SDK de plugins. Colunas extras, decoração personalizada de cartão, layouts filtrados por tenant ou substituições completas com tab.override são todos expressáveis sem bifurcar este plugin.

Para desligar sem remover: acrescente dashboard.plugins.kanban.enabled: false ao config.yaml (ou apague o plugins/kanban/dashboard/manifest.json).

Fronteira de escopo​

A GUI é fina de propósito. Tudo o que o plugin faz é alcançável pela CLI; o plugin só deixa isso confortável para humanos. Atribuição automática, orçamentos, portões de governança e visões de organograma seguem no espaço do usuário — um perfil roteador, outro plugin, ou reaproveitar o tools/approval.py — exatamente como está listado na seção de fora de escopo da especificação de projeto.

Referência de comandos da CLI​

Esta é a superfície que você (ou scripts, o cron, o painel) usa para conduzir o quadro. Os workers rodando dentro do dispatcher usam a superfície de ferramentas kanban_* para as mesmas operações — a CLI daqui e as ferramentas de lá passam pelo kanban_db, então as duas superfícies concordam por construção.

hermes kanban init # cria o kanban.db + imprime dica do daemon
hermes kanban create "<title>" [--body ...] [--assignee <profile>]
[--parent <id>]... [--tenant <name>]
[--workspace scratch|worktree|worktree:<path>|dir:<path>]
[--branch <name>]
[--priority N] [--triage] [--idempotency-key KEY]
[--max-runtime 30m|2h|1d|<seconds>]
[--max-retries N]
[--goal] [--goal-max-turns N]
[--skill <name>]...
[--json]
hermes kanban list [--mine] [--assignee P] [--status S] [--tenant T] [--archived]
[--workflow-template-id <id>] [--current-step-key <key>]
[--sort created|created-desc|priority|priority-desc|status|assignee|title|updated]
[--json]
hermes kanban show <id> [--json]
hermes kanban assign <id> <profile> # ou 'none' para desatribuir
hermes kanban reassign <id>... <profile> # reatribui tarefas em lote a um perfil
hermes kanban edit <id> [--title ...] [--body ...] # edita título / corpo / prioridade no lugar
[--priority N]
hermes kanban promote <id>... # move tarefas todo/blocked para ready (recuperação)
hermes kanban schedule <id> --at <ISO8601> # define/limpa o horário de início da tarefa
hermes kanban diagnostics [--json] # retrato de saúde do quadro (apelido: diag)
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]

# Verbos em lote — aceitam vários ids:
hermes kanban complete <id>... [--result "..."]
hermes kanban block <id> "<reason>" [--ids <id>...]
hermes kanban unblock <id>...
hermes kanban archive <id>...

hermes kanban request-review <id> [--summary "..."] [--metadata JSON] [--reviewer PROFILE]
hermes kanban request-changes <id> "<required changes>" # revisor ativo -> implementador
hermes kanban reopen-review <id>... [--reason "..."] # mudanças pedidas: 'review' -> ready/todo

hermes kanban tail <id> # acompanha o fluxo de eventos de uma tarefa
hermes kanban watch [--assignee P] [--tenant T] # transmite TODOS os eventos ao terminal, ao vivo
[--kinds completed,blocked,…] [--interval SECS]
hermes kanban heartbeat <id> [--note "..."] # sinal de vida do worker em operações longas
hermes kanban runs <id> [--json] # histórico de tentativas (uma linha por execução)
hermes kanban assignees [--json] # perfis em disco + contagem de tarefas por responsável
hermes kanban dispatch [--dry-run] [--max N] # passada única
[--failure-limit N] [--json]
hermes kanban daemon --force # DESCONTINUADO — dispatcher autônomo (use `hermes gateway start`)
[--failure-limit N] [--pidfile PATH] [-v]
hermes kanban stats [--json] # contagens por status + por responsável
hermes kanban log <id> [--tail BYTES] # log do worker, de ~/.hermes/kanban/logs/
hermes kanban notify-subscribe <id> # hook de ponte do gateway (usado pelo /kanban no gateway)
--platform <name> --chat-id <id> [--thread-id <id>] [--user-id <id>]
[--chat-type dm|group|channel|thread] [--delivery-mode notify|notify+wake|wake]
hermes kanban notify-list [<id>] [--json]
hermes kanban notify-unsubscribe <id>
--platform <name> --chat-id <id> [--thread-id <id>]
hermes kanban context <id> # o que um worker enxerga
hermes kanban specify [<id> | --all] [--tenant T] # detalha uma ideia da coluna de triagem
[--author NAME] [--json] # numa especificação completa e promove para todo
hermes kanban gc [--event-retention-days N] # espaços de trabalho + eventos antigos + logs antigos
[--log-retention-days N]

Todos os comandos também estão disponíveis como comando de barra na CLI interativa e no gateway de mensagens (veja o comando de barra /kanban mais abaixo).

O --max-retries é uma sobrescrita de disjuntor por tarefa para o dispatcher. --max-retries 1 bloqueia a tarefa na primeira tentativa malsucedida, enquanto --max-retries 3 permite duas repetições e bloqueia na terceira falha. Omita para usar o kanban.failure_limit do config.yaml e, depois dele, o padrão embutido.

Configuração de concorrência, agendamento e promoção de filhas​

Chave de configuraçãoPadrãoO que faz
kanban.max_in_progressnão definido (ilimitado)Limita quantas tarefas rodam ao mesmo tempo. Quando o quadro já tem N rodando, o dispatcher deixa de criar mais — útil com workers lentos (LLMs locais, máquinas com poucos recursos), para eles terminarem o que têm antes de acumular mais e estourar o tempo. Valores inválidos ou menores que 1 geram um aviso e se comportam como ilimitado.
kanban.max_in_progress_per_profilenão definido (ilimitado)Variante por perfil do max_in_progress — limita quantas tarefas um único perfil responsável pode rodar ao mesmo tempo. Útil quando um perfil está lento ou limitado por taxa mas os outros devem continuar fluindo. Vale junto com o max_in_progress do quadro inteiro; os dois precisam liberar para a criação acontecer.
kanban.auto_promote_childrentrueDepois que o decompose_triage_task() produz filhas sem dependência bloqueadora de pai, elas são promovidas automaticamente para ready, para o dispatcher pegá-las. Use false para exigir revisão manual — as filhas ficam em todo até você promover.
kanban.default_workdirnão definidoDiretório de trabalho padrão no nível do quadro, aplicado a tarefas novas quando nem --workspace nem a própria tarefa sobrescrevem. O workspace: por tarefa continua ganhando.
kanban:
max_in_progress: 2
auto_promote_children: false
default_workdir: ~/work/active-project

Início agendado de tarefas (scheduled_at)​

Defina scheduled_at numa tarefa para adiar o despacho até um horário específico. O dispatcher pula tarefas prontas cujo scheduled_at está no futuro e as pega no primeiro tick depois daquele instante.

hermes kanban create "auditoria noturna de backup" \
--assignee ops --scheduled-at "2026-06-01T03:00:00Z"

Proteção contra recriação​

O dispatcher se recusa a recriar uma tarefa pronta quando ela bateu num erro de cota/autenticação/429 na execução anterior (blocker_auth), ou concluiu uma execução com sucesso dentro da janela de proteção (recent_success), ou quando um comentário recente da tarefa aponta para um PR do GitHub (active_pr). Isso evita tempestades repetidas de workers sobre o mesmo bug ou tarefa enquanto um humano se atualiza. Veja a linha respawn_guarded na referência de eventos.

Arrastar para excluir e exclusão em lote (painel)​

O painel expõe uma zona de descarte na página do kanban — arraste qualquer cartão até ela para excluir a tarefa (em cascata por task_events, vínculos filhos e assinaturas). Uma confirmação protege contra acidentes. A exclusão em lote também é alcançável por DELETE /api/plugins/kanban/tasks com um corpo JSON {"ids": ["t_abc", "t_def", ...]}.

Endpoints de visibilidade de workers​

A API de plugins do painel agora expõe estes endpoints só de leitura (mais um verbo de controle de execução) para monitores externos:

EndpointDevolve
GET /api/plugins/kanban/workers/activeWorkers criados no momento, com PID, perfil, id da tarefa, horário de início e último heartbeat
GET /api/plugins/kanban/runs/{id}Detalhe de uma execução — id da tarefa, status, início/fim, código de saída, caminho do log
POST /api/plugins/kanban/runs/{run_id}/terminateEncerra uma execução recuperável — para o worker e libera a tarefa para redespacho
GET /api/plugins/kanban/inspectRetrato combinado do dispatcher — fila, contagem em andamento x max_in_progress, eventos recentes

Todos eles passam pela mesma autenticação de plugin do painel que o resto da API do plugin de kanban.

Ajudante de topologia Kanban Swarm​

O hermes kanban swarm cria de uma vez um grafo durável no formato Kanban Swarm v1: um cartão raiz/quadro-negro já concluído, N cartões de worker em paralelo, um cartão verificador travado em todos os workers, e um cartão sintetizador travado no verificador. O contexto compartilhado do enxame (o "quadro-negro") é guardado como comentários JSON estruturados no cartão raiz, para qualquer worker poder ler.

hermes kanban swarm "Desenhar um plano de failover multirregião" \
--workers researcher,architect,sre \
--verifier reviewer --synthesizer writer

O grafo resultante é gravado atomicamente: dispatchers e leitores do painel veem ou nenhum enxame novo, ou a topologia completa, nunca um grafo raiz/worker/verificador parcialmente vinculado. Depois disso ele despacha normalmente — os workers rodam em paralelo, o verificador acorda quando todos terminam, e o sintetizador acorda quando o verificador marca o trabalho como limpo.

Comando de barra /kanban​

Todo verbo hermes kanban <action> também é alcançável como /kanban <action> — de dentro de uma sessão interativa de hermes chat e de qualquer plataforma do gateway (Telegram, Discord, Slack, WhatsApp, Signal, Matrix, Mattermost, e-mail, SMS). As duas superfícies chamam exatamente o mesmo ponto de entrada hermes_cli.kanban.run_slash(), que reaproveita a árvore de argparse do hermes kanban, então a superfície de argumentos, as flags e o formato de saída são idênticos na CLI, no /kanban e no hermes kanban. Você não precisa sair do chat para conduzir o quadro.

/kanban list
/kanban show t_abcd
/kanban create "escrever post de lançamento" --assignee writer --parent t_research
/kanban comment t_abcd "ficou bom, pode publicar"
/kanban unblock t_abcd
/kanban dispatch --max 3
/kanban specify t_abcd # transforma uma frase da triagem numa especificação de verdade
/kanban specify --all --tenant engineering # varre todas as tarefas de triagem de um tenant

Coloque argumentos com mais de uma palavra entre aspas, do mesmo jeito que faria num shell — o run_slash interpreta o resto da linha com shlex.split, então tanto "..." quanto '...' funcionam.

Uso no meio da execução: o /kanban passa por cima da proteção de agente ocupado​

O gateway normalmente enfileira comandos de barra e mensagens de usuário enquanto um agente ainda está pensando — é isso que impede você de começar sem querer um segundo turno com o primeiro em andamento. O /kanban está explicitamente isento dessa proteção. O quadro mora em ~/.hermes/kanban.db, não no estado do agente em execução, então leituras (list, show, context, tail, watch, stats, runs) e escritas (comment, unblock, block, assign, archive, create, link, …) passam na hora, mesmo no meio de um turno.

Esse é o ponto inteiro da separação:

  • Um worker bloqueia esperando por um par → você manda /kanban unblock t_abcd do celular e o dispatcher pega o par no próximo tick. O worker bloqueado não é interrompido — ele só deixa de estar bloqueado.
  • Você percebe um cartão que precisa de contexto humano → /kanban comment t_xyz "use o schema de 2026, não o de 2025" cai na thread da tarefa e a próxima execução dela vai ler isso no kanban_show().
  • Você quer saber o que a sua frota está fazendo sem parar o orquestrador → /kanban list --mine ou /kanban stats inspeciona o quadro sem tocar na sua conversa principal.

Assinatura automática no /kanban create (só no gateway)​

Quando você cria uma tarefa pelo gateway com /kanban create "…", o chat de origem (plataforma + id do chat + id da thread) é automaticamente inscrito nos eventos terminais daquela tarefa (completed, blocked, gave_up, crashed, timed_out). Você recebe uma mensagem por evento terminal — incluindo a primeira linha do resumo do worker no completed — sem ter que ficar consultando nem lembrar o id da tarefa.

você> /kanban create "transcrever o podcast de hoje" --assignee transcriber
bot> Created t_9fc1a3 (ready, assignee=transcriber)
(subscribed — you'll be notified when t_9fc1a3 completes or blocks)

… ~8 minutos depois …

bot> ✓ t_9fc1a3 completed by transcriber
transcritos 42 minutos, salvo em podcast/2026-05-04.md

As assinaturas sobrevivem à tarefa chegar em done — a conclusão é reversível (um revisor ou controlador pode reabrir uma tarefa concluída), então a sessão de origem continua sendo notificada ao longo dos ciclos de reabertura. Elas se removem sozinhas em archived (o estado final irreversível). Em quadros que nunca arquivam, uma varredura de GC limpa as assinaturas de tarefas que ficaram em done sem atividade nova por kanban.done_sub_retention_days dias (padrão 30; use 0 para desligar), para que linhas velhas não se acumulem para sempre. Se você programa uma criação com --json (saída de máquina), a assinatura automática é pulada — a suposição é que quem chama por script quer gerenciar assinaturas explicitamente por /kanban notify-subscribe.

Uma assinatura automática vinda de chat é criada no modo notify+wake: num evento terminal, o agente de destino recebe a mensagem passiva e toma um turno de verdade, então consegue ler o contexto do quadro e responder na própria voz. Veja Modos de entrega mais abaixo.

Truncamento de saída em mensagens​

As plataformas de gateway têm limites práticos de tamanho de mensagem. Se /kanban list, /kanban show ou /kanban tail produzirem mais de ~3800 caracteres de saída, a resposta é truncada com um rodapé … (truncated; use \hermes kanban …` in your terminal for full output)`. A superfície de CLI não tem esse limite.

Autocompletar​

Na CLI interativa, digitar /kanban e apertar Tab percorre a lista embutida de subcomandos (list, ls, show, create, assign, link, unlink, claim, comment, complete, block, unblock, archive, tail, dispatch, context, init, gc). Os demais verbos listados na referência de CLI acima (watch, stats, runs, log, assignees, heartbeat, notify-subscribe, notify-list, notify-unsubscribe, daemon) também funcionam — só ainda não estão na lista de sugestões do autocompletar.

Padrões de colaboração​

O quadro dá conta destes oito padrões sem nenhuma primitiva nova:

PadrãoFormatoExemplo
P1 LequeN irmãs, mesmo papel"pesquise 5 ângulos em paralelo"
P2 Pipelinecadeia de papéis: batedor → editor → redatormontagem do resumo diário
P3 Votação / quórumN irmãs + 1 agregador3 pesquisadores → 1 revisor escolhe
P4 Diário de longa duraçãomesmo perfil + diretório compartilhado + croncofre do Obsidian
P5 Humano no circuitoworker bloqueia → usuário comenta → desbloqueiadecisões ambíguas
P6 @mentionroteamento em linha a partir da prosa@reviewer look at this
P7 Espaço de trabalho por thread/kanban here numa threadthreads de gateway por projeto
P8 Frota em sérieum perfil, N sujeitos50 contas sociais
P9 Especificador de triagemideia crua → triage → hermes kanban specify expande o corpo → todo"transforme esta frase numa tarefa especificada"

Para exemplos trabalhados de cada um, veja docs/hermes-kanban-v1-spec.pdf.

Um vínculo de pai não é só uma trava de agendamento — é o canal de repasse de contexto de um cartão concluído para um novo. Quando você cria um cartão com --parent <done-card-id>, duas coisas acontecem:

  1. Ele já nasce elegível. O create_task define o status pelo estado dos pais: um filho cujos pais estão todos done é criado direto em ready — sem espera, sem promoção manual. (Filhos de pais ainda abertos ficam em todo até o recompute_ready promovê-los quando o último pai terminar.)
  2. O repasse do pai vai junto. O contexto de worker montado para o filho (build_worker_context, o que o kanban_show() devolve) contém uma seção ## Parent task results com o summary e o metadata de conclusão de cada pai, na íntegra:
## Parent task results
### t_77c26979 (completed just now)
Adicionado backoff exponencial com jitter ao helper de repetição.
_metadata_: `{"changed_files": ["hermes_cli/retry.py", "tests/test_retry.py"], "decisions": ["backoff limitado a 60s", "jitter = full"]}`

É por isso que o padrão para trabalho de continuação sobre um cartão terminado é um novo cartão filho, não reabrir o cartão concluído. Cartões concluídos são história imutável — o contexto deles flui para frente pelo vínculo de pai. Retrabalho no mesmo cartão (laços de repetição num cartão que falha) é outro mecanismo: as tentativas anteriores no mesmo cartão aparecem como "prior attempts" no contexto daquele cartão.

Uma worktree ou branch sozinha não substitui isso: o estado do repositório diz ao worker de continuação como o código está, mas não por quê — as decisões, os testes rodados e os arquivos tocados moram no repasse estruturado do pai, não no git. Evidência que não existia quando o pai concluiu (por exemplo, um log de CI que falhou depois) pertence ao corpo do cartão novo.

# O cartão de implementação t_impl está pronto. A CI falha duas horas depois.
hermes kanban create "Corrigir falha de CI vinda de t_impl: test_retry falha intermitente no 3.11" \
--assignee coder \
--parent t_impl \
--body "$(cat <<'EOF'
A execução de CI #4812 falhou depois que t_impl entrou.
Trecho do log: FAILED tests/test_retry.py::test_backoff_jitter - TimeoutError
Aceite: tests/test_retry.py verde no 3.11 e no 3.12 na CI.
Use uma worktree/branch nova; não force-push na branch original.
EOF
)"

O worker de remediação nasce com o resumo e os metadados do cartão original (arquivos mudados, decisões) já em contexto, mais a evidência fresca que você colocou no corpo.

Reconciliar branches de worker que colidem​

Em pipelines de engenharia (P1/P2 com worktrees), as branches de dois workers podem conflitar na hora do merge. Não deixe nenhum dos dois julgar a própria causa — o agente que colidiu não tem o contexto do par e, com confiabilidade, sobrescreve o outro lado ou abandona o próprio trabalho. Em vez disso, crie um cartão de reconciliação atribuído a um terceiro perfil, neutro, com os dois cartões em conflito vinculados como pais: os vínculos de pai levam os resumos de conclusão dos dois lados para o contexto do reconciliador, então ele recebe os dois diffs e as duas intenções. A skill embutida merge-reconciler dá a esse worker o procedimento completo: classificar cada trecho em conflito, resolver com imparcialidade, verificar e devolver um resumo nomeando cada decisão.

Pontos quentes de colisão em campanhas paralelas​

Em campanhas largas, alguns arquivos viram ímãs de colisão: muitos workers acrescentam um pouco cada no mesmo arquivo, ninguém se responsabiliza por mantê-lo pequeno, e ele vira o palco de conflitos de merge constantes. A mitigação é uma convenção de comentário, não uma primitiva nova. Um worker que percebe que o diff dele continua colidindo com o das irmãs em um arquivo — ou que um arquivo que ele toca continua aparecendo nos comentários recentes de outros cartões — não deve continuar empilhando em silêncio. Em vez disso, ele deixa um comentário no próprio cartão com um prefixo reconhecível:

hotspot: hermes_cli/kanban_db.py — terceira edição conflitante no laço de despacho nesta leva

e repete o alerta no metadata de conclusão. Orquestradores (ou humanos revisando o quadro) que virem dois ou mais comentários hotspot: apontando o mesmo caminho devem criar um cartão dedicado de refatoração/decomposição para aquele arquivo antes de enfileirar mais trabalho que o toque — dividir o arquivo-ímã sai mais barato do que reconciliar toda colisão futura que ele causaria. Para conflitos que já aconteceram, use o padrão de cartão de reconciliação acima, com a skill merge-reconciler; sinalizar pontos quentes é a correção a montante que impede o reconciliador de virar uma faixa fixa de trabalho.

Uso multi-tenant​

Quando uma frota de especialistas atende vários negócios, marque cada tarefa com um tenant:

hermes kanban create "relatório mensal" \
--assignee researcher \
--tenant business-a \
--workspace dir:~/tenants/business-a/data/

Os workers recebem $HERMES_TENANT e prefixam as escritas de memória com esse namespace. O quadro, o dispatcher e as definições de perfil são todos compartilhados; só os dados têm escopo.

Notificações no desktop​

O plugin de Kanban do aplicativo Desktop expõe os mesmos eventos terminais nativamente — sem precisar de plataforma de gateway. Enquanto o socket de eventos ao vivo do quadro Kanban estiver conectado, cada evento completed, blocked, gave_up, crashed, timed_out ou de roteamento para triagem (block_loop_detected) levanta um toast dentro do app com o repasse do worker (resumo, motivo do bloqueio ou erro) e uma ação "Open Kanban". Quando você está longe da janela do Hermes, o mesmo evento também dispara uma notificação nativa do sistema operacional (controlada por Settings ▸ Notifications ▸ Plugin notifications), então uma tarefa que bate num bloqueio enquanto você está em outro app ainda chega até você.

Janela de cobertura: as notificações do desktop pegam carona no fluxo de eventos ao vivo, então só disparam com o app rodando e o plugin Kanban habilitado. Eventos que caem com o app fechado não são reproduzidos como notificação na abertura seguinte — use uma assinatura de gateway (abaixo) para entrega que precisa sobreviver ao app fechado.

Notificações pelo gateway​

Quando você roda /kanban create … pelo gateway (Telegram, Discord, Slack etc.), o chat de origem é automaticamente inscrito na tarefa nova. O notificador em segundo plano do gateway consulta a task_events a cada poucos segundos e entrega uma mensagem por evento terminal (completed, blocked, gave_up, crashed, timed_out) naquele chat. Tarefas concluídas também mandam a primeira linha do --result do worker, para você ver o desfecho sem precisar de /kanban show.

Você pode gerenciar assinaturas explicitamente pela CLI — útil quando um script / tarefa de cron quer notificar um chat de onde ele não se originou:

hermes kanban notify-subscribe t_abcd \
--platform telegram --chat-id 12345678 --thread-id 7 \
--chat-type group --delivery-mode notify+wake
hermes kanban notify-list
hermes kanban notify-unsubscribe t_abcd \
--platform telegram --chat-id 12345678 --thread-id 7

Uma assinatura se remove sozinha assim que a tarefa chega em done ou archived; não precisa limpar nada.

Modos de entrega​

O --delivery-mode controla como o notificador reage a um evento terminal. Toda assinatura está em um de três modos (notify é o padrão e o comportamento original):

ModoMensagem passivaAcorda o agenteUse quando
notifysimnãoVocê só quer um aviso no chat (padrão).
notify+wakesimsimVocê também quer que o agente de destino tome um turno de verdade — ler o contexto do quadro e responder na própria voz. As assinaturas automáticas vindas de chat usam este modo.
wakenãosimVocê só quer que o agente aja sobre o evento, sem um aviso separado.

Um "wake" forja uma mensagem sintética de entrada para o agente de gateway de destino, de forma que ele tome um turno normal (lê o comentário + o resultado, raciocina, responde) em vez de receber uma notificação passiva de uma linha. Isso só acontece quando o notificador roda dentro de um processo de gateway vivo; fora disso, uma assinatura notify+wake ainda entrega a mensagem passiva, enquanto uma assinatura só de wake não faz nada naquele processo.

Quais eventos acordam. Os que devolvem uma decisão à origem: completed, blocked, gave_up, crashed, timed_out, review_requested (um worker terminou a implementação e repassou via kanban_request_review) e block_loop_detected (a tarefa foi roteada para triage depois de bloqueios repetidos). status, archived e unblocked são entregues mas nunca acordam — são transições de contabilidade, não decisões. Quando um evento completed ou review_requested carrega um resumo, esse repasse pega carona no turno de despertar, então o agente acordado vê o que o worker realmente fez.

O --chat-type (dm | group | channel | thread) registra o tipo do chat de origem, para que um turno de despertar resolva a sessão real do operador: o build_session_key chaveia grupos, canais e threads de forma diferente das DMs, então um chat_type impreciso rotearia o despertar para uma sessão separada e sem contexto. A assinatura automática do /kanban e os caminhos de comando de barra capturam isso sozinhos — você só define à mão ao inscrever um chat a partir de um script ou do cron. Omita para deixar uma assinatura existente inalterada (assinaturas novas assumem dm).

Configurações com vários perfis: a entrega pertence ao perfil​

Numa implantação de um gateway por perfil (um dispatcher, processos de gateway separados para writer, admin etc. — veja o guia de vários gateways), o despacho e a entrega têm donos separados:

  • O despacho continua com um dono só. Exatamente um gateway mantém kanban.dispatch_in_gateway: true e roda o dispatcher; todo outro gateway define como false.
  • A entrega de notificações pertence ao perfil. Todo gateway — inclusive os que não despacham — roda o notificador e consulta apenas as assinaturas carimbadas com um perfil cujos adaptadores de plataforma ele hospeda. Uma tarefa criada pelo Telegram do perfil writer tem a mensagem de completed/blocked entregue pelo gateway do writer, mesmo que o gateway default tenha feito o despacho.
  • Assinaturas antigas criadas antes do carimbo de perfil (sem notifier_profile na linha) são entregues apenas pelo gateway que segura a trava singleton do dispatcher de verdade, para que dois gateways nunca disputem essas assinaturas.

A entrega duplicada entre gateways é impedida pela reivindicação atômica por evento no banco do quadro. Não são necessários relays, compartilhamento de credenciais nem dispatchers extras — cada gateway de perfil simplesmente entrega pelos próprios adaptadores.

Execuções (runs) — uma linha por tentativa​

Uma tarefa é uma unidade lógica de trabalho; uma execução (run) é uma tentativa de executá-la. Quando o dispatcher reivindica uma tarefa pronta, ele cria uma linha em task_runs e aponta tasks.current_run_id para ela. Quando aquela tentativa termina — concluída, bloqueada, travada, com tempo esgotado, com falha de criação, recuperada —, a linha de execução fecha com um outcome e o ponteiro da tarefa é limpo. Uma tarefa tentada três vezes tem três linhas em task_runs.

Por que duas tabelas em vez de só alterar a tarefa: você precisa do histórico completo de tentativas para postmortems do mundo real ("a segunda tentativa do revisor chegou a aprovar, a terceira fez o merge"), e precisa de um lugar limpo para pendurar metadados por tentativa — quais arquivos mudaram, quais testes rodaram, quais achados um revisor anotou. Esses são fatos da execução, não da tarefa.

As execuções também são onde mora o repasse estruturado. Quando um worker conclui uma tarefa (via kanban_complete(...)), ele pode passar:

  • summary (parâmetro da ferramenta) / --summary (CLI) — repasse legível por humanos; fica na execução; as filhas seguintes o veem no build_worker_context delas.
  • metadata (parâmetro da ferramenta) / --metadata (CLI) — dicionário JSON livre na execução; as filhas o veem serializado ao lado do resumo.
  • result (parâmetro da ferramenta) / --result (CLI) — linha curta de log que fica na linha da tarefa (campo legado, mantido por compatibilidade).

As filhas seguintes leem o resumo + metadados da execução concluída mais recente de cada pai. Workers repetindo uma tarefa leem as tentativas anteriores na própria tarefa (desfecho, resumo, erro), para não repetirem um caminho que já falhou.

# O que um worker de fato faz — uma chamada de ferramenta, de dentro do laço do agente:
kanban_complete(
summary="implementei o token bucket, chaveia por user_id com fallback de IP, todos os testes passam",
metadata={"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14},
result="limitador de taxa entregue",
)

O mesmo repasse é alcançável pela CLI quando você (humano) precisa fechar uma tarefa que um worker não consegue — por exemplo, uma tarefa abandonada, ou uma que você marcou como pronta manualmente pelo painel:

hermes kanban complete t_abcd \
--result "limitador de taxa entregue" \
--summary "implementei o token bucket, chaveia por user_id com fallback de IP, todos os testes passam" \
--metadata '{"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14}'

# Revise o histórico de tentativas de uma tarefa repetida:
hermes kanban runs t_abcd
# # OUTCOME PROFILE ELAPSED STARTED
# 1 blocked worker 12s 2026-04-27 14:02
# → BLOCKED: precisa de decisão sobre a chave do limite de taxa
# 2 completed worker 8m 2026-04-27 15:18
# → implementei o token bucket, chaveia por user_id com fallback de IP

As execuções aparecem no painel (seção Run History na gaveta, uma linha colorida por tentativa) e na API REST (GET /api/plugins/kanban/tasks/:id devolve um array runs[]). O PATCH /api/plugins/kanban/tasks/:id com {status: "done", summary, metadata} encaminha os dois ao kernel, então o botão "mark done" do painel é equivalente à CLI. As linhas de task_events carregam o run_id a que pertencem, para a interface poder agrupá-las por tentativa, e o evento completed embute o resumo de primeira linha no payload (limitado a 400 caracteres), para os notificadores do gateway renderizarem repasses estruturados sem uma segunda ida ao SQL.

Ressalva de fechamento em lote. O hermes kanban complete a b c --summary X é recusado — repasse estruturado é por execução, então copiar e colar o mesmo resumo em N tarefas é quase sempre errado. Fechar em lote sem --summary / --metadata continua funcionando, para o caso comum de "terminei uma pilha de tarefas administrativas".

Execuções recuperadas por mudança de status. Se você arrasta uma tarefa em execução para fora de running no painel (de volta para ready, ou direto para todo), ou arquiva uma tarefa que ainda estava rodando, a execução em andamento fecha com outcome='reclaimed' em vez de ficar órfã. A linha de task_runs está sempre num estado terminal quando tasks.current_run_id é NULL, e vice-versa — essa invariante vale na CLI, no painel, no dispatcher e no notificador.

Execuções sintéticas para conclusões nunca reivindicadas. Concluir ou bloquear uma tarefa que nunca foi reivindicada (por exemplo, um humano fecha pelo painel uma tarefa em ready com um resumo, ou um usuário de CLI roda hermes kanban complete <ready-task> --summary X) faria o repasse se perder. Em vez disso, o kernel insere uma linha de execução de duração zero (started_at == ended_at) carregando o resumo / metadados / motivo, para o histórico de tentativas ficar completo. O run_id do evento completed / blocked aponta para essa linha.

Atualização ao vivo da gaveta. Quando o fluxo de eventos por WebSocket do painel reporta eventos novos para a tarefa que o usuário está vendo, a gaveta se recarrega (por um contador de eventos por tarefa enfiado na lista de dependências do useEffect). Fechar e reabrir não é mais necessário para ver a linha nova de uma execução ou um desfecho atualizado.

Compatibilidade futura​

Duas colunas anuláveis em tasks estão reservadas para o roteamento de fluxos de trabalho da v2: workflow_template_id (a qual template esta tarefa pertence) e current_step_key (qual passo daquele template está ativo). O kernel v1 as ignora no roteamento mas deixa os clientes escreverem nelas, então um release v2 pode acrescentar a maquinaria de roteamento sem outra migração de schema.

Referência de eventos​

Toda transição acrescenta uma linha em task_events. Cada linha carrega um run_id opcional, para as interfaces agruparem eventos por tentativa. Os tipos se agrupam em três blocos, o que facilita filtrar (hermes kanban watch --kinds completed,gave_up,timed_out):

Ciclo de vida (o que mudou na tarefa como unidade lógica):

TipoPayloadQuando
created{assignee, status, parents, tenant}Tarefa inserida. run_id é NULL.
promoted—todo → ready porque todos os pais chegaram a done. run_id é NULL.
claimed{lock, expires, run_id}O dispatcher reivindicou atomicamente uma tarefa em ready para criação.
completed{result_len, summary?}O worker escreveu --result / --summary e a tarefa chegou a done. summary é o repasse de primeira linha (limite de 400 caracteres); a versão completa mora na linha da execução. Se o complete_task é chamado numa tarefa nunca reivindicada com campos de repasse, uma execução de duração zero é sintetizada para o run_id continuar apontando para algo.
blocked{reason, kind, recurrences}Worker ou humano virou a tarefa para blocked. kind é o motivo tipado do bloqueio (needs_input, capability, transient, ou null para bloqueio genérico); recurrences é o contador do laço de desbloqueio. Sintetiza uma execução de duração zero quando chamado numa tarefa nunca reivindicada com --reason.
dependency_wait{reason, kind}O worker bloqueou com kind=dependency — a tarefa só está esperando outra tarefa, então ela vai para todo (travada pelo pai, promovida automaticamente) em vez de blocked. Sem humano no meio.
block_loop_detected{reason, kind, recurrences, limit}Uma tarefa foi desbloqueada e rebloqueada pelo mesmo motivo BLOCK_RECURRENCE_LIMIT vezes (padrão 2). Em vez de cair em blocked de novo — onde um cron ficaria desbloqueando —, ela vai para triage para uma decisão humana, quebrando o laço desbloqueio↔rebloqueio.
unblocked—blocked → ready (ou todo, se os pais ainda estão abertos), manualmente ou via /unblock. Zera o consecutive_failures do dispatcher mas preserva de propósito o block_recurrences, para o disjuntor de laço manter a memória. run_id é NULL.
archived—Escondida do quadro padrão. Se a tarefa ainda estava rodando, carrega o run_id da execução recuperada como efeito colateral.

Edições (mudanças feitas por humanos que não são transições):

TipoPayloadQuando
assigned{assignee}Responsável mudou (inclusive desatribuição).
edited{fields}Título ou corpo atualizado.
reprioritized{priority}Prioridade mudou.
status{status}O arrastar-e-soltar do painel escreveu um status direto (ex.: todo → ready). Carrega o run_id da execução recuperada ao arrastar para fora de running; fora isso, run_id é NULL.

Telemetria de worker (sobre o processo de execução, não sobre a tarefa lógica):

TipoPayloadQuando
spawned{pid}O dispatcher iniciou com sucesso um processo de worker.
heartbeat{note?}O worker chamou hermes kanban heartbeat $TASK para sinalizar que está vivo durante operações longas.
reclaimed{stale_lock}O TTL da reivindicação expirou sem conclusão; a tarefa volta para ready.
crashed{pid, claimer}O PID do worker não está mais vivo, mas o TTL ainda não tinha expirado.
timed_out{pid, elapsed_seconds, limit_seconds, sigkill}max_runtime_seconds estourado; o dispatcher mandou SIGTERM (depois SIGKILL, após 5 s de tolerância) e reenfileirou.
stale{elapsed_seconds, last_heartbeat_at, heartbeat_age_seconds, timeout_seconds, pid, terminated}A tarefa rodou mais que kanban.dispatch_stale_timeout_seconds (padrão 4 h) E nenhum kanban_heartbeat chegou na última hora. O dispatcher mandou SIGTERM no worker local (se havia um) e devolveu a tarefa a ready para redespacho. NÃO incrementa o contador de falhas (obsolescência é detecção de ausência do lado do dispatcher, não culpa do worker). Workers em operações longas devem chamar kanban_heartbeat pelo menos uma vez por hora para evitar isso.
reconciled{reason, claim_lock, claim_expires, worker_pid}Reconciliação de cartão órfão: o cartão estava em running com a contabilidade de reivindicação quebrada (claim_lock ou claim_expires NULL — travamento no meio da reivindicação, SQL manual, restauração do banco) e sem worker vivo, então nenhum dos caminhos de TTL/travamento/obsolescência conseguiria recuperá-lo. O dispatcher o reenfileirou em ready com um comentário explicativo. Controlado por kanban.reconcile_orphans no config.yaml (padrão true).
respawn_guarded{reason}O dispatcher se recusou a recriar esta tarefa pronta neste tick. Motivos: blocker_auth (a última falha foi erro de cota/autenticação/429 — espere a janela de taxa reiniciar), recent_success (houve uma execução concluída na última hora — espere a revisão antes de rodar de novo), active_pr (uma URL de PR do GitHub aparece num comentário recente — algum worker anterior já abriu um PR). A tarefa fica em ready; o próximo tick tem outra chance de criar o worker. Se a condição de base persistir, o disjuntor normal de consecutive_failures bloqueia automaticamente via gave_up depois de failure_limit falhas.
spawn_failed{error, failures}Uma tentativa de criação falhou (PATH faltando, espaço de trabalho impossível de montar, …). O contador incrementa; a tarefa volta para ready para nova tentativa.
protocol_violation{pid, claimer, exit_code, protocol_violation}O worker saiu com sucesso enquanto a tarefa ainda estava running, normalmente porque respondeu sem chamar kanban_complete ou kanban_block. Emitido em toda violação (o marcador protocol_violation: true do payload é copiado para os metadados da execução e alimenta o orçamento de repetição só de violações). Abaixo do orçamento — até _PROTOCOL_VIOLATION_FAILURE_LIMIT (padrão 3) violações consecutivas, com o max_retries por tarefa sobrescrevendo — a tarefa simplesmente volta para ready para outra tentativa; quando a sequência bate no limite, o dispatcher também emite gave_up e bloqueia automaticamente.
gave_up{failures, effective_limit, limit_source, error}O disjuntor disparou depois de N tentativas malsucedidas consecutivas. A tarefa se bloqueia sozinha com o último erro. O limite efetivo resolve como max_retries da tarefa, depois failure_limit do dispatcher / kanban.failure_limit, depois o padrão embutido.

O hermes kanban tail <id> mostra esses eventos para uma única tarefa. O hermes kanban watch os transmite para o quadro inteiro.

Fora de escopo​

O Kanban é de host único de propósito. O ~/.hermes/kanban.db é um arquivo SQLite local e o dispatcher cria os workers na mesma máquina. Rodar um quadro compartilhado entre dois hosts não é suportado — não existe primitiva de coordenação para "worker X no host A, worker Y no host B", e o caminho de detecção de travamento assume que os PIDs são locais ao host. Se você precisa de vários hosts, rode um quadro independente por host e use delegate_task / uma fila de mensagens para ligá-los.

Especificação de projeto​

O projeto completo — arquitetura, correção de concorrência, comparação com outros sistemas, plano de implementação, riscos, questões em aberto — mora em docs/hermes-kanban-v1-spec.pdf. Leia isso antes de abrir qualquer PR que mude comportamento.