Quando o Hermes trava esperando um humano, seu plugin agora consegue saber


São duas da manhã e a sua execução do Hermes num servidor esbarra num comando que pede senha de sudo, ou abre um prompt de aprovação para um comando perigoso. Não tem ninguém ali. A execução simplesmente para até dar timeout e falhar, e às nove da manhã seguinte você encontra uma linha fria de saída no terminal. O que você queria era trivial: quando travar, me avise.

Construir isso costumava ser chato. O Hermes para esperar uma pessoa em vários pontos sem relação entre si — o pedido de senha de sudo, uma pergunta da ferramenta clarify, uma aprovação de comando perigoso — e cada um vive no seu próprio caminho de código. Para ter uma única notificação de “alguém é necessário”, você tinha que registrar um hook por tipo de prompt e depois casar padrões nas strings de comando do terminal para adivinhar quais eram sudo.

O PR #133171, mesclado em 5 de outubro de 2026, fecha essa lacuna com um par de hooks genérico que dispara sempre que o agente para e espera por uma pessoa.

O par de hooks

on_human_input_request   — fires right before the prompt is shown
on_human_input_resolved  — fires once per request when the wait ends

O hook de request fica no código compartilhado por onde toda superfície já passa, então CLI, Ink TUI / Desktop, ACP e plataformas de gateway (Telegram, Discord, …) ficam todos cobertos, sem nenhuma fiação por superfície. O que ele cobre:

kind Qual prompt Onde aparece
"sudo" Prompt mascarado de senha de sudo antes de rodar um comando sudo CLI interativo, Ink TUI / Desktop (card sudo.request), fallback via /dev/tty
"clarify" Uma pergunta da ferramenta clarify CLI, Ink TUI / Desktop, plataformas de gateway
"approval" Aprovação de comando perigoso / de escrita, consentimento de elicitation do MCP, transports de aprovação de plugin CLI, Ink TUI / Desktop, ACP, plataformas de gateway, plugins usando ctx.register_approval_transport

Dois casos deliberadamente não disparam: decisões de approvals.mode=smart tomadas pelo modelo auxiliar, porque ninguém é consultado; e aprovações no gateway que se juntam a um prompt idêntico já pendente, porque a pessoa vê um único prompt e o par de hooks dispara só uma vez por ele.

O que o seu callback recebe

def my_callback(kind: str, request_id: str, session_id: str, session_key: str,
                platform: str, prompt: str, **kwargs):
    ...
Parâmetro Descrição
kind "sudo", "clarify" ou "approval"; novos tipos podem ser adicionados, então ignore os que você não trata
request_id Único por prompt; o on_human_input_resolved correspondente carrega o mesmo valor
session_id ID da sessão do Hermes quando conhecido; caso contrário, ""
session_key Chave de roteamento de sessão/chat, útil para delimitar notificações por chat
platform Plataforma de gateway ("telegram", "discord", …) ou origem local ("cli", "tui", "desktop", …)
prompt O que se pergunta à pessoa: o comando, no caso de sudo/approval; o texto da pergunta, no caso de clarify

Um detalhe sobre o prompt merece um parágrafo próprio. Ele sempre passa pelo redator de segredos (redact_sensitive_text(..., force=True)), mesmo quando security.redact_secrets está desligado na sua config. Se a própria redação falhar, o prompt vira string vazia — o texto bruto nunca é enviado. A senha de sudo que você digita e as respostas às perguntas de clarify nunca são passadas a nenhum dos hooks; só o rótulo de resultado é.

Os valores de retorno são ignorados, e um callback que lança exceção é registrado no nível debug enquanto o prompt segue — um observador quebrado não consegue quebrar um prompt humano. Os callbacks rodam na thread que está prestes a bloquear, então mantenha-os rápidos: mande chamadas de push ou webhook para uma thread em segundo plano.

Valores de resultado

O on_human_input_resolved dispara exatamente uma vez por requisição, depois que a espera termina, com um campo extra:

kind valores de outcome
"sudo" "provided", "skipped" (resposta vazia), "timeout", "cancelled", "error"
"clarify" o resultado da própria superfície: "submitted", "timed_out", "cancelled", "undelivered", "terminated" ou "error"
"approval" "once", "session", "always", "deny", "timeout", "cancelled", "notify_failed" ou "transport_<failure>" para um transport de plugin que falhou

Um exemplo realmente útil: só me chame se ninguém responder

A necessidade comum não é “me notifique a cada prompt”, mas “me cutuque se ninguém cuidar disso”. O par request/resolve torna isso natural:

import threading

def push_notification(text):
    ...  # chame seu serviço de push aqui

def register(ctx):
    pending = {}

    def on_request(kind, request_id, prompt, session_key, **kwargs):
        pending[request_id] = threading.Timer(
            30, push_notification, (f"Hermes needs you ({kind}): {prompt[:80]}",)
        )
        pending[request_id].start()  # só aciona se ninguém responder em 30s

    def on_resolved(request_id, **kwargs):
        timer = pending.pop(request_id, None)
        if timer:
            timer.cancel()

    ctx.register_hook("on_human_input_request", on_request)
    ctx.register_hook("on_human_input_resolved", on_resolved)

A propriedade boa aqui é que prompts respondidos por um humano nunca chegam até você, e só execuções de fato travadas acionam o push. Como o request_id é pareado, você não recebe pushes duplicados nem timers órfãos. Para a forma do sistema de hooks como um todo — hooks observadores versus hooks interceptadores, registro e ciclo de vida — veja a referência do comando hooks e o guia anterior de hooks de plugin de provider.

Status da release

O par de hooks está na main e ainda em nenhuma tag publicada (a release mais recente é a v0.21.5, tag v2026.9.24). Instalações via Git o pegam ao puxar a main mais recente; imagens Docker e implantações hospedadas o recebem com a próxima tag. Para saber como escrever o próprio plugin, veja a referência do comando plugins.

Por que isto importa além das notificações

Construir um alerta de “o Hermes precisa de um humano” exigia conhecer os caminhos internos de código de cada tipo de prompt. Agora é um par de hooks, um campo kind e um conjunto de rótulos de resultado. A ideia mais transferível é a decisão de design por trás disso: tratar “parou e está esperando um humano” como um evento de primeira classe, em vez de deixar cada chamador montar a sua própria visão. Para automação sem supervisão, isso costuma ser a diferença entre descobrir uma execução travada na manhã seguinte e saber dela no instante em que ela empaca.