O seu alerta foi para o chat do grupo — agora uma pergunta de acompanhamento ali realmente funciona

São 2 da manhã, o pipeline de deploy fica vermelho e o webhook empurra o alerta para o seu Telegram. Você vê isso na manhã seguinte, responde “é a mesma falha de ontem?” — e recebe de volta “a quem você está se referindo?”. Você acabou de dizer a ele. Ele não faz ideia. Isso não é o modelo sendo lento; é assim que as entregas de webhook são projetadas: cada evento roda até o fim na própria sessão, e a mensagem que foi entregue não existe no histórico do chat de destino. Este post é sobre o interruptor adicionado na v0.21.5 e sobre o que ele corrige e o que não corrige.
De onde vem essa lacuna
Uma rota de webhook funciona assim: um serviço externo faz POST de um evento, o payload é renderizado num prompt pelo seu template, uma execução do agent acontece e o resultado é entregue a um chat. Ao longo de tudo isso, a sessão webhook:<route>:<delivery_id> é efêmera — ela existe para aquela execução e desaparece depois.
Quando você fala com o agent naquele chat do Telegram, ele carrega a sessão daquele chat. Os dois históricos nunca se cruzam, então:
- ele não sabe que acabou de te enviar um alerta;
- “a falha de ontem” não aponta para nada no histórico dele;
- a sua única opção é repetir o alerta ou desistir do acompanhamento.
Ativando o interruptor
Uma rota agora pode aderir ao espelhamento. Adicione uma flag onde você cria ou atualiza a inscrição:
hermes webhook subscribe ci-alerts \
--prompt "build {route} failed: {payload}" \
--deliver telegram \
--mirror-to-session
A saída do subscribe ganha uma linha:
Created webhook subscription: ci-alerts
URL: https://…/webhooks/ci-alerts
Profile: default
Events: (all)
Deliver: telegram
Replies: each delivery is mirrored into the target chat's session
Rotas existentes podem ser alteradas na configuração, em vez disso:
platforms:
webhook:
extra:
routes:
ci-alerts:
prompt: "build {route} failed: {payload}"
deliver: telegram
mirror_to_session: true
A checagem de valor aqui é deliberadamente rígida: só um booleano true de verdade adere. A string "true" não serve — o espelhamento grava no seu histórico de conversa, então quem escreve a rota precisa pedir por ele explicitamente.
O que realmente é espelhado
Não a requisição HTTP bruta. A própria mensagem entregue, com um rótulo:
[Webhook delivery: ci-alerts]
build 4821 failed on main
Ela é anexada à sessão do chat de destino como um turno user com autoridade de usuário, pelo mesmo caminho de código e convenção de papel dos briefs de cron continuáveis. Por que user e não assistant? Porque aquele texto não é o agent da sessão de destino falando — é você tendo sido informado de algo. Armazenado como turno assistant, ele produziria assistant → assistant no histórico, o que quebra de vez os providers de alternância estrita; armazenado como user, dois turnos user consecutivos se fundem com segurança e o formato da conversa sobrevive.
O ganho: responda “é a mesma falha de ontem?” e o agent consegue ver o rótulo e o texto entregue no próprio histórico, então ele consegue de fato responder.
Três limites que você precisa conhecer antes de ativar
1. É opt-in, desligado por padrão
Rotas sem mirror_to_session se comportam exatamente como antes. A mesma lógica do mirror_delivery do cron: nada é gravado numa sessão a menos que seja pedido.
2. Tem escopo de profile
O espelhamento roda dentro de _profile_scope(profile), então ele grava no state.db do profile que é dono da rota. Esse detalhe não é cosmético: um chat_id de DM do Telegram é o ID de usuário da mesma pessoa em todos os bots, então um espelhamento sem escopo cairia na DM de outro profile com essa mesma pessoa. Vale ter isso em mente, especialmente em rotas /p/<profile>/.
3. É best-effort
Uma falha no espelhamento nunca faz uma entrega falhar — a implementação nunca levanta exceção — e o espelhamento é pulado quando o chat de destino ainda não tem uma sessão de gateway (ninguém falou com o agent ali). Em outras palavras: ativar o interruptor não garante que você consiga fazer o acompanhamento. Se o grupo nunca teve uma conversa com o agent, não há sessão onde gravar.
Quando não ativar
Cuidado com rotas deliver_only. Elas pulam o agent por completo — o template renderizado é o corpo literal da mensagem, com custo zero de token — e o texto espelhado é, portanto, o payload renderizado bruto.
Então tudo que puder influenciar esse texto cai no seu histórico de conversa como um turno user:
- o webhook de um issue tracker público: o que um estranho conseguir escrever no título de uma issue pode aparecer no seu histórico de conversa;
- corpos de alerta brutos de um sistema de monitoramento: vale se eles carregarem campos controlados pelo usuário (mensagens de erro, caminhos de requisição, strings de user-agent).
A regra é simples: espelhe apenas fontes em cujo conteúdo você confia — os seus próprios serviços, os seus próprios pipelines. Para qualquer coisa em que um terceiro possa escrever, pense duas vezes antes de ligar o interruptor.
Confirmando que funcionou
hermes webhook list
A lista mostra os detalhes de cada rota, e criar ou atualizar uma inscrição imprime a linha Replies: each delivery is mirrored into the target chat's session.
Para checar o comportamento real, o teste mais barato é um evento de hermes webhook test para a rota, seguido de perguntar no chat de destino “o que você acabou de me enviar?” — se ele responder em vez de perguntar o que você quer dizer, o espelhamento está funcionando.
Como isso se encaixa no resto
Isto e o registro de entregas com resposta duplicada são dois lados do mesmo problema no nível do gateway: aquele impede que uma resposta seja entregue duas vezes, este impede que uma entrega fique invisível para a conversa depois.
Se você já roda checagens periódicas via cron, os briefs de cron têm a flag equivalente mirror_delivery há um tempo. As entregas de webhook agora se igualam a ela, e os dois caminhos usam a mesma convenção (um rótulo mais um turno user), então fazer o acompanhamento parece idêntico nos dois casos.
A versão curta
- O comportamento padrão não mudou: as entregas de webhook são fire-and-forget e o chat de destino não sabe de nada.
- Com
mirror_to_session: true(ou--mirror-to-session), o conteúdo entregue é gravado na sessão do chat de destino como[Webhook delivery: <route>]mais um turno user. - Três limites: desligado por padrão, com escopo de profile, best-effort (pulado quando não há sessão).
- Em rotas
deliver_onlyo texto espelhado é o payload bruto — ative apenas para fontes em que você confia.
Isto chegou na v0.21.5 (tag v2026.9.24), parte daquela janela de patch sem release notes — para as outras coisas já utilizáveis dela, veja o guia de autoverificação da janela da v0.21.5 e a nossa página de release da v0.21.5. A tabela completa de flags está na referência de comando.