Observando um loop: a contagem regressiva ao vivo que acabou de chegar ao CLI, à TUI e ao Desktop


Você configurou um loop para acompanhar um deploy a cada três minutos e voltou ao trabalho. Dez minutos depois você olha para o terminal e ele ainda diz a mesma coisa que dizia quando você começou — next in 3m — então você se faz a pergunta que todo usuário de loop acaba fazendo: isso está vivo, ou parou em silêncio? É essa ambiguidade que um PR integrado em 15 de outubro se propôs a eliminar, e ele fez isso nas três superfícies de uma vez.

O que é um loop permanente, em um parágrafo

/loop reexecuta um prompt dentro da sua sessão atual numa cadência que você escolhe, ou numa cadência que ele mesmo escolhe:

/loop 5m check the deploy status and tell me if it's live yet

Cada wakeup é um turno comum do agente, então ele lê o estado atual — o resultado mais recente do CI, a profundidade da fila, o arquivo como ele está agora — em vez de repetir um resumo. Ele roda no CLI, na TUI, no chat do dashboard, no app de desktop e em todas as plataformas de gateway, e vive naquela sessão: as condições de parada são LOOP_COMPLETE vindo do agente, --times N, --until <condition>, loops.max_ticks como orçamento de segurança (100 por padrão), ou você, com /loop stop e /loop pause.

O problema: uma contagem regressiva pintada uma única vez

O comportamento antigo era uma única linha de transcript — ↻ Loop: next in 3m. — impressa quando o loop era aceito. Ela nunca se movia. No app de desktop era pior de uma forma mais sutil: o next ~45m do cartão do loop era calculado uma vez por renderização, então o número ficava congelado no valor que tinha quando o cartão desenhou pela primeira vez. A informação que você precisava (quanto falta até o próximo wakeup, está rodando agora, está pausado) existia na sessão, mas nada a repintava.

O que as três superfícies mostram agora

CLI. O dock de trabalho ao vivo — o painel que já acompanha goals, subagentes, processos e a fila — ganha uma linha de loop:

↻ loop · every 3m · 1 run · next in 2m · <prompt>

A contagem regressiva avança a cada minuto e passa a segundos no último minuto, então um prompt ocioso se repinta no máximo uma vez por minuto em vez de uma vez por segundo. Durante um wakeup ela mostra running now, e quando o loop está pausado exibe o motivo da pausa. Recolha o dock e o cabeçalho carrega um badge compacto — ↻ loop next in 2m — para você ainda ter a resposta sem expandir nada. Com o dock visível, a antiga linha estática ↻ Loop: next in 3m. do transcript é descartada, porque manter as duas era só ruído duplicado.

TUI. O hermes --tui ganha uma LoopBar logo abaixo da GoalBar, com o mesmo texto, alimentada pelo snapshot da sessão e por um relógio de um segundo que só repinta quando o rótulo realmente muda.

Desktop. O cartão do loop agora compartilha o relógio de contagem regressiva de um segundo do heartbeat — next 00:02:09 — e, uma vez passado o prazo, diz due — waiting for idle em vez de mostrar um número que ficou negativo. Essa última string é compartilhada com o cartão do heartbeat, e é por isso que ela foi renomeada para uma chave comum em vez de duplicada.

A parte que você não notaria: o gateway teve que publicar isso

Uma contagem regressiva no CLI é direta — aquele processo é dono do seu terminal. As barras do Ink na TUI e o cartão do app de desktop são diferentes: eles leem um snapshot de sessão que o gateway publica como session.control.update. E esse update só era publicado quando alguém clicava num botão de um cartão do desktop. Digite /loop 3m … num chat e o estado nunca chegava às superfícies que deveriam renderizá-lo.

A correção move a publicação para os lugares onde o estado do loop de fato muda: o owner publica depois de um comando de barra de automação, depois dos hooks de goal e de loop pós-turno de uma sessão local, e depois que um wakeup de loop dispara ou um tick de barra se assenta. O caminho legado tui_gateway ganha as mesmas duas publicações, para que clientes mais antigos também vejam os wakeups.

Essa é a lição interessante deste PR: adicionar uma exibição a um valor ao vivo raramente é só uma mudança de exibição. Se o valor é publicado por outra pessoa, a correção inclui convencê-la a publicar nos momentos certos — caso contrário você entrega uma barra de progresso que só é honesta no primeiro frame.

Como saber se você já tem isso

A mudança foi integrada em 15 de outubro às 15:34 UTC, e a v0.22.2 já havia sido publicada naquela manhã, então por enquanto isso está só na main — trabalho que está apenas na main sai na v0.23.0. Checagem prática:

hermes version

Se você está numa build de release, o dock continuará mostrando a linha estática antiga. Se você precisa de visibilidade do loop hoje e não pode rodar a main, o substituto viável é /loop status, que reporta cadência, ticks disparados e tempo até o próximo wakeup sob demanda — uma tecla em vez de uma olhada de relance, mas responde a “está vivo”.

Dois detalhes relacionados vale saber antes de confiar numa contagem regressiva: um wakeup é um turno normal com papel de usuário, e o custo em tokens escala com a cadência, então um loop de um minuto que não tem nada a fazer ainda custa um turno por minuto — prefira o ritmo automático quando você está esperando algo que muda no ritmo dele. E para trabalho que precisa sobreviver ao fechamento do seu terminal, esta é a ferramenta errada: um agendamento sem supervisão pertence a um cron job, enquanto o /loop é para monitoramento dentro de uma sessão em que você já está.

Também vale saber

  • loops.min_interval_seconds (30), loops.self_paced_floor_seconds (60) e loops.self_paced_ceiling_seconds (900) são os knobs por trás da cadência; loops.max_ticks (100, 0 para ilimitado) é o freio de segurança.
  • Um /goal ativo é dono da sessão: enquanto um goal está sendo conduzido pelo seu judge, os wakeups do loop ficam adiados e retomam quando o goal termina, pausa ou estaciona numa wait barrier.
  • Um loop por sessão — definir um novo /loop substitui o antigo; vários loops significam várias sessões (ou cron).
  • No desktop e no dashboard, uma sessão com um loop ativo fica isenta do reaper de sessões desconectadas, então os wakeups continuam disparando depois que você fecha a janela — desde que o próprio processo de backend continue rodando.
  • Ctrl+C num turno de wakeup pausa o loop em vez de destruí-lo; /loop resume retoma de onde parou.

Se o /loop em si é novidade para você, o guia do comando loop cobre o básico sobre o qual esta contagem regressiva se apoia.


Fontes: PR #139187 (/loop shows a live countdown to the next wakeup on CLI, TUI and Desktop, integrado em 15 de outubro de 2026) e sua tabela de validação; website/docs/user-guide/features/loops.md na main para os comandos, os modos de cadência, as chaves de configuração e a comparação entre /loop, /goal e cron.