Pular para o conteúdo principal

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

Tarefas agendadas (cron)

Agende tarefas para rodar sozinhas, em linguagem natural ou em expressões cron. O Hermes expõe o gerenciamento por uma ferramenta única, a cronjob, com operações por ação, em vez de ferramentas separadas para agendar, listar e remover.

O que o cron faz hoje​

Tarefas de cron conseguem:

  • agendar execuções únicas ou recorrentes
  • pausar, retomar, editar, disparar e remover tarefas
  • anexar nenhuma, uma ou várias skills a uma tarefa
  • entregar o resultado na conversa de origem, em arquivos locais, ou nos destinos configurados
  • rodar em sessões novas de agente, com a lista normal de ferramentas
  • rodar em modo sem agente — um script no horário marcado, com a saída entregue literal, sem modelo nenhum (veja modo sem agente)

Tudo isso está disponível ao próprio Hermes pela ferramenta cronjob, então você cria, pausa, edita e remove tarefas pedindo em português — sem CLI.

dica

Em qual modelo uma tarefa de cron roda? A resolução na hora do disparo é: modelo fixado na tarefa → cron.model no config.yaml → o padrão global do hermes model.

  • Fixado na tarefa — definido por você, pelo painel, por hermes cron create/edit --model … --provider …, ou editando o ~/.hermes/cron/jobs.json. Uma vez definido, fica. A ferramenta cronjob do agente não define nem muda o modelo por tarefa — isso é decisão do usuário.
  • cron.model / cron.model_provider — um padrão para toda a frota de cron: toda tarefa sem modelo fixado roda nele, independentemente do seu modelo de conversa. Defina uma vez (hermes config set cron.model <nome>) e trocar o modelo de conversa com hermes model ou /model nunca mexe na sua frota.
  • Padrão global — só quando nenhum dos dois está definido é que a tarefa segue o hermes model. Nesse caso o Hermes tira um retrato do provedor e do modelo na criação, e se o padrão global mudar depois, a tarefa falha fechada: ela pula a execução, não faz chamada de inferência, e avisa você uma vez — e continua pulada (e silenciosa) nos disparos seguintes, até você agir ou a configuração voltar. Em tarefas recorrentes, fixe o provedor e o modelo explicitamente (hermes cron edit <id> --provider <provedor> --model <modelo>) para seguir. Isso impede que uma tarefa sem supervisão herde em silêncio uma troca para um provedor ou modelo pago. Definir o cron.model (ou fixar por tarefa) é o jeito deliberado de rotear o gasto de cron, e a proteção não age no eixo já coberto por ele. Quem prefere que as tarefas sem fixação sigam o padrão global pode desligar a proteção.

O hermes setup --portal é a opção de menor atrito para execuções sem supervisão, porque a renovação do OAuth é automática.

dica

Esforço de raciocínio por tarefa. Uma tarefa pode fixar o próprio nível de pensamento, independentemente do modelo: none, minimal, low, medium, high, xhigh, max ou ultra. Definido, ele vence tanto o agent.reasoning_effort global quanto o agent.reasoning_overrides por modelo, naquelas execuções (none desliga o pensamento). Defina com hermes cron create/edit --reasoning-effort high; passe uma string vazia na edição para limpar e voltar a seguir a configuração. (De propósito, isso não é exposto na ferramenta cronjob do agente — configuração de modelo é decisão do usuário.) Níveis que o modelo não suporta são limitados ou omitidos pelo provedor na hora — fixar xhigh num modelo que vai até high roda em high. A fixação não tem efeito em tarefas no_agent (não há chamada de modelo a ajustar). Use para rodar análises pesadas em high, enquanto tarefas recorrentes baratas rodam em minimal, sem tocar no seu padrão global.

aviso

Sessões de cron não podem criar mais tarefas de cron recursivamente. O Hermes desliga as ferramentas de gerenciamento dentro das execuções, para evitar laços de agendamento sem controle.

Criar tarefas​

Na conversa, com /cron​

/cron add 30m "Me lembre de conferir a compilação"
/cron add "every 2h" "Confira a situação do servidor"
/cron add "every 1h" "Resuma os itens novos do feed" --skill blogwatcher
/cron add "every 1h" "Use as duas skills e combine o resultado" --skill blogwatcher --skill maps

Pela linha de comando​

hermes cron create "every 2h" "Confira a situação do servidor"
hermes cron create "every 1h" "Resuma os itens novos do feed" --skill blogwatcher
hermes cron create "every 1h" "Use as duas skills e combine o resultado" \
--skill blogwatcher \
--skill maps \
--name "Combo de skills"

Conversando​

Peça normalmente:

Toda manhã às 9h, veja as notícias de IA no Hacker News e me mande um resumo no Telegram.

O Hermes usa a ferramenta cronjob por dentro.

Validação antes do disparo​

Antes de montar qualquer coisa para uma execução agendada, o agendador confere se a configuração da tarefa consegue mesmo produzir uma execução bem-sucedida:

  • se a chave de API do provedor resolve (pulado quando há uma cadeia de fallback_providers configurada, já que a reserva pode salvar uma chave principal ausente),
  • se as skills anexadas estão prontas (sem variáveis de ambiente, comandos ou arquivos de credencial faltando),
  • se os destinos de entrega são conhecidos e têm credencial de gateway configurada (destinos local e origin nunca são conferidos).

Quando a validação falha, o last_status da tarefa vira blocked_config, UM alerta é entregue (não se repete a cada disparo) e nenhuma chamada de modelo é feita — uma tarefa mal configurada nunca gasta token. A próxima execução saudável limpa o estado bloqueado, então uma quebra futura volta a alertar.

Para desligar a validação e voltar ao comportamento antigo (a execução segue e falha no meio):

cron:
preflight: false

Ou: hermes config set cron.preflight false.

Deixar tarefas sem fixação seguirem o padrão global​

A proteção contra deriva de modelo e provedor vem ligada. Se as suas tarefas sem fixação devem mesmo seguir toda troca global, desligue no config.yaml:

cron:
model_drift_guard: false

Ou pelo comando de configuração:

hermes config set cron.model_drift_guard false

Isso desliga tanto o bloqueio em execução quanto o aviso mostrado quando a configuração global muda. Os retratos existentes continuam gravados, então voltar a opção para true religa a proteção sem recriar tarefa nenhuma.

aviso

Com a proteção desligada, tarefas sem supervisão herdam a mudança de padrão global na hora. Uma troca para um provedor ou modelo pago passa a gastar dinheiro em toda execução agendada.

Tarefas com skills​

Uma tarefa de cron pode carregar uma ou mais skills antes de rodar o prompt.

Uma skill​

cronjob(
action="create",
skill="blogwatcher",
prompt="Check the configured feeds and summarize anything new.",
schedule="0 9 * * *",
name="Morning feeds",
)

Várias skills​

As skills são carregadas na ordem. O prompt vira a instrução da tarefa, em cima delas.

cronjob(
action="create",
skills=["blogwatcher", "maps"],
prompt="Look for new local events and interesting nearby places, then combine them into one short brief.",
schedule="every 6h",
name="Local brief",
)

Útil quando você quer um agente agendado herdando fluxos reaproveitáveis, sem enfiar o texto inteiro da skill no prompt do cron.

Rodar uma tarefa dentro de um projeto​

Por padrão, tarefas de cron rodam soltas de qualquer repositório — nenhum AGENTS.md, CLAUDE.md ou .cursorrules é carregado, e as ferramentas de terminal, arquivo e execução de código rodam a partir de onde o gateway subiu. Passe --workdir (na CLI) ou workdir= (na chamada de ferramenta) para mudar:

# Pela CLI (o agendamento e o prompt são posicionais)
hermes cron create "every 1d at 09:00" \
"Audite os PRs abertos, resuma a saúde do CI e poste em #eng" \
--workdir /home/eu/projetos/acme
# Da conversa, pela ferramenta cronjob
cronjob(
action="create",
schedule="every 1d at 09:00",
workdir="/home/eu/projetos/acme",
prompt="Audite os PRs abertos, resuma a saúde do CI e poste em #eng",
)

Com o workdir definido:

  • o AGENTS.md, o CLAUDE.md e o .cursorrules daquele diretório entram no prompt de sistema (na mesma ordem de descoberta da CLI interativa)
  • o terminal, o read_file, o write_file, o patch, o search_files e o execute_code usam aquele diretório
  • o caminho precisa ser um diretório absoluto que exista — caminhos relativos e diretórios inexistentes são recusados na criação e na atualização
  • passe --workdir "" (ou workdir="" pela ferramenta) na edição para limpar e voltar ao comportamento antigo
Execução em série

Tarefas com workdir rodam em sequência no disparo do agendador, e não no grupo paralelo. É de propósito: o trabalhador de cron aplica o diretório da tarefa por estado global do processo, então duas tarefas com workdir ao mesmo tempo corromperiam o diretório uma da outra. Tarefas sem workdir continuam em paralelo.

Editar tarefas​

Não é preciso apagar e recriar uma tarefa só para mudá-la.

Como referenciar

O <job_id> abaixo também aceita o nome da tarefa (sem diferenciar maiúsculas) — útil quando você lembra do morning-digest, mas não do identificador hexadecimal. Um identificador exato tem precedência; se a referência não é um identificador e mais de uma tarefa tem aquele nome, o comando recusa e imprime os candidatos, para você desempatar.

Na conversa​

/cron edit <job_id> --schedule "every 4h"
/cron edit <job_id> --prompt "Use a tarefa revisada"
/cron edit <job_id> --skill blogwatcher --skill maps
/cron edit <job_id> --remove-skill blogwatcher
/cron edit <job_id> --clear-skills

Pela linha de comando​

hermes cron edit <job_id> --schedule "every 4h"
hermes cron edit <job_id> --prompt "Use a tarefa revisada"
hermes cron edit <job_id> --skill blogwatcher --skill maps
hermes cron edit <job_id> --add-skill maps
hermes cron edit <job_id> --remove-skill blogwatcher
hermes cron edit <job_id> --clear-skills

Observações:

  • --skill repetido substitui a lista de skills anexadas
  • --add-skill acrescenta, sem substituir
  • --remove-skill tira skills específicas
  • --clear-skills remove todas

Ciclo de vida​

Na conversa​

/cron list
/cron pause <job_id>
/cron resume <job_id>
/cron run <job_id>
/cron remove <job_id>

Pela linha de comando​

hermes cron list
hermes cron pause <id_ou_nome>
hermes cron resume <id_ou_nome>
hermes cron run <id_ou_nome>
hermes cron remove <id_ou_nome>
hermes cron edit <id_ou_nome> [...opções]
hermes cron status
hermes cron tick

O que cada um faz:

  • pause — mantém a tarefa, mas para de agendá-la
  • resume — reabilita e calcula a próxima execução futura
  • run — dispara no próximo tique do agendador
  • remove — apaga de vez
  • edit — muda agendamento, prompt, entrega e o resto

Busca por nome. Os cinco verbos que mudam algo (pause, resume, run, remove, edit) e a ferramenta cronjob do agente aceitam o nome da tarefa (sem diferenciar maiúsculas) no lugar do identificador. Tanto o agente quanto a CLI preferem um identificador exato, se existir; nomes ambíguos (mais de uma tarefa com o mesmo nome) são recusados, com a lista completa de candidatos. Nomes não são únicos, então essa proteção carrega peso — ela evita mexer em silêncio na tarefa errada.

Tarefas de cron que gerenciam tarefas de cron​

Por padrão, agentes abertos pelo agendador não usam a ferramenta cronjob — uma tarefa agendada não cria, edita nem remove outras. Para permitir, no config.yaml:

cron:
allow_agent_scheduling: true # padrão: false

Habilitado, um agente agendado gerencia a tabela de cron como qualquer sessão de conversa: agenda tarefas únicas de continuação a partir do trabalho agendado, ajusta a própria cadência, ou roda uma tarefa "bibliotecária", que reconcilia a tabela inteira (lista, e então atualiza, remove ou cria). Duas propriedades mantêm isso sob controle:

  • Uma tabela única, do usuário. Tarefas criadas de dentro de uma execução caem no mesmo jobs.json de todas as outras, sem posse especial — você lista, edita ou remove exatamente como se tivesse criado.
  • Sem entrega órfã. Uma execução de cron é efêmera, então o deliver: origin de dentro de uma é resolvido na criação para o destino concreto da tarefa que criou (plataforma:id_da_conversa[:id_do_tópico], ou local se ela não entrega em lugar nenhum). Uma tarefa criada por um agente agendado nunca aponta a saída para uma sessão que não existe mais. Destinos explícitos (local, all, telegram:<id>) são respeitados literalmente.

Prefira prompts que atualizam tarefas existentes (listar primeiro, depois atualizar pelo identificador) a prompts que criam tarefas novas a cada execução.

Como funciona​

A execução do cron é responsabilidade do daemon do gateway. O gateway tica o agendador a cada 60 segundos, rodando as tarefas devidas em sessões isoladas.

hermes gateway install # instala como serviço de usuário
sudo hermes gateway install --system # Linux: serviço de sistema, no boot, para servidores
hermes gateway # ou roda em primeiro plano

hermes cron list
hermes cron status

Comportamento do agendador​

A cada tique, o Hermes:

  1. carrega as tarefas de ~/.hermes/cron/jobs.json
  2. compara o next_run_at com a hora atual
  3. abre uma sessão de AIAgent nova para cada tarefa devida
  4. injeta as skills anexadas naquela sessão, se houver
  5. roda o prompt até o fim
  6. entrega a resposta final
  7. atualiza os metadados e o próximo horário

Uma trava de arquivo em ~/.hermes/cron/.tick.lock impede que tiques sobrepostos rodem o mesmo lote duas vezes.

Histórico de execução​

O Hermes registra cada tentativa reivindicada no banco local do perfil, ~/.hermes/cron/executions.db, antes de despachar. As tentativas passam por claimed e running, e terminam num estado imutável: completed, failed ou unknown. Depois de um reinício, o Hermes só marca uma tentativa abandonada como unknown quando o PID original e a impressão digital de início do processo provam que o dono sumiu. Tentativas desconhecidas são registro de auditoria, e nunca são repetidas sozinhas.

Veja as tentativas recentes com hermes cron runs [id] --limit 20 (apelido: history). O histórico terminal é limitado; tentativas ativas nunca são podadas. O registro entra nos backups rápidos.

Aviso de falhas repetidas​

Cada tarefa acompanha um failure_streak — execuções falhadas em sequência (falha de entrega não conta). Uma execução que falha antes mesmo de chegar ao agente — um import quebrado depois de uma atualização pela metade, um cliente de provedor que não consegue ser construído — conta e alerta igual. Quando a sequência de uma tarefa recorrente atinge o limiar, a mensagem de falha entregue na conversa ganha um aviso, dizendo que a tarefa falhou N vezes seguidas e sugerindo corrigir, pausar (hermes cron pause <tarefa>) ou remover. Qualquer execução bem-sucedida zera a sequência, e o hermes cron list mostra a contagem ao lado da última execução da tarefa. Tarefas de execução única nunca avisam.

cron:
failure_nudge_threshold: 3 # padrão; 0 desliga o aviso

Incidentes de falha: reconhecer uma falha conhecida​

Uma tarefa recorrente que falha sempre com o mesmo erro cutuca você a cada execução. Cada falha também é registrada como um incidente durável, indexado pela tarefa mais uma assinatura normalizada do texto do erro, no mesmo banco de registro do histórico.

hermes cron incidents # lista (a atividade mais recente primeiro)
hermes cron incidents --state alerted # filtra: detected | alerted | closed
hermes cron incidents ack <id> # reconhece — para de avisar

Reconhecer um incidente silencia o aviso por execução só daquela assinatura exata. Nada mais muda: o histórico ainda registra toda falha, a sequência continua contando, e no instante em que a tarefa começar a falhar com um erro diferente, um incidente novo é criado e os alertas voltam. Uma execução bem-sucedida não mexe nos incidentes — eles são por assinatura, não por tarefa.

Ciclo de vida do incidente: detected (falha registrada) → alerted (pelo menos um aviso chegou à entrega) → closed (reconhecido; terminal para aquela assinatura). O texto do erro guardado é higienizado de segredos e cortado antes de ser escrito.

O registro está sempre ligado e não custa nada ignorar — nenhum aviso é suprimido até você fazer o ack explicitamente.

Opções de entrega​

Ao agendar, você diz para onde a saída vai:

OpçãoDescriçãoExemplo
"origin"De volta para onde a tarefa foi criadaPadrão nas plataformas de mensagem
"local"Só em arquivos locais (~/.hermes/cron/output/)Padrão na CLI
"telegram"Canal principal do TelegramUsa o TELEGRAM_HOME_CHANNEL
"telegram:123456"Conversa específica, pelo identificadorEntrega direta
"telegram:-100123:17585"Tópico específicoFormato id_da_conversa:id_do_tópico
"discord"Canal principal do DiscordUsa o DISCORD_HOME_CHANNEL
"discord:#engenharia"Canal específicoPelo nome
"slack"Canal principal do Slack
"whatsapp"WhatsApp
"signal"Signal
"matrix"Sala principal do Matrix
"mattermost"Canal principal do Mattermost
"email"E-mail
"sms"SMS pela Twilio
"homeassistant"Home Assistant
"dingtalk"DingTalk
"feishu"Feishu/Lark
"wecom"WeCom
"weixin"Weixin (WeChat)
"bluebubbles"BlueBubbles (iMessage)
"qqbot"QQ Bot
"bot-chat"A conversa canônica de bot deste perfil — o bot lê a saída e respondeSó na máquina local
"bot-chat:research"A conversa de bot de outro perfil localValidado na criação
"all"Espalha por todos os canais principais conectadosResolvido na hora do disparo
"telegram,discord"Espalha por um conjunto específicoLista separada por vírgula
"origin,all"Entrega na origem e em todos os outros canais conectadosCombine os que quiser

A resposta final do agente é entregue automaticamente ao destino em deliver: — o agente não manda mensagem sozinho, então não há nada a chamar dentro do prompt.

Entrega em conversa de bot (bot-chat)​

O bot-chat entrega a saída dentro da sessão canônica de "conversa de bot" de um perfil, como mensagem de verdade. Diferente de todos os outros destinos — em que quem recebe é uma pessoa lendo um canal — aqui quem recebe é o próprio bot: ele recebe a saída como mensagem que chega, age no que precisa de ação, e responde na conversa dele. Use quando a saída agendada deve ser processada, e não só publicada.

  • bot-chat sozinho aponta para o próprio perfil da tarefa.
  • bot-chat:<perfil> aponta para outro perfil na mesma máquina. Os nomes são validados contra o hermes profile list na criação; perfis em outros gateways ou máquinas nunca podem ser alvo, então perfis de mesmo nome em máquinas diferentes não são ambíguos.
  • Cada entrega custa um turno completo de agente ao bot de destino — cuidado com a frequência.
  • Combina com outros destinos (bot-chat,telegram), mas nunca entra no all.

Intenção de roteamento (all)​

O all manda uma tarefa para todo canal de mensagem que você configurou, sem precisar enumerar. Ele é resolvido na hora do disparo, então uma tarefa criada antes de você ligar o Telegram passa a incluí-lo no tique seguinte à definição do TELEGRAM_HOME_CHANNEL.

Semântica: o all se expande para toda plataforma com canal principal configurado. Zero é aceitável; a tarefa simplesmente não produz destino e é registrada como falha de entrega.

O all se combina com destinos explícitos. O origin,all entrega na conversa de origem mais todos os outros canais principais conectados, sem duplicar por (plataforma, conversa, tópico).

Tópico de cron no Telegram (TELEGRAM_CRON_THREAD_ID)​

Com o modo de tópicos do Telegram ligado, a conversa raiz é reservada como um saguão de sistema — respostas mandadas ali são recusadas com um lembrete, e o reply_to_message_id é descartado, então não dá para responder a uma mensagem de cron que caiu na conversa principal.

Aponte o cron para um tópico dedicado:

  1. No Telegram, abra a conversa com o bot e crie um tópico chamado Cron, por exemplo. Segure o cabeçalho do tópico → Copiar link; o número no fim é o message_thread_id.
  2. Defina TELEGRAM_CRON_THREAD_ID=<esse número> no seu .env.

Isso vale só para entregas de cron. O TELEGRAM_HOME_CHANNEL_THREAD_ID (usado em outros lugares, como avisos de reinício) não muda. Destinos explícitos, como deliver="telegram:id_conversa:id_tópico", continuam vencendo a variável. As respostas às mensagens de cron agora chegam na sessão do tópico, então dá para agir direto nelas.

Embrulho da resposta​

Por padrão, a saída entregue vem com cabeçalho e rodapé, para quem recebe saber que veio de uma tarefa agendada:

Cronjob Response: Morning feeds
-------------

<saída do agente aqui>

Note: The agent cannot see this message, and therefore cannot respond to it.

Para entregar a saída crua, sem embrulho, coloque cron.wrap_response em false:

# ~/.hermes/config.yaml
cron:
wrap_response: false

Tarefas continuáveis (responder a uma entrega de cron)​

Por padrão, uma entrega de cron é do tipo dispare e esqueça: a mensagem é enviada, mas não vive no histórico da conversa, então, se você responder, o agente não tem registro do que disse. Marque uma tarefa como continuável e o resumo entregue vira uma conversa em que dá para responder — o agente tem o resumo no contexto, em vez de perguntar "que Tarefa #2?".

É opcional, e vem desligado. Habilite globalmente na configuração, ou por tarefa, pelo attach_to_session da ferramenta cronjob (que sobrescreve o global naquela tarefa):

# ~/.hermes/config.yaml
cron:
mirror_delivery: false # true torna as entregas continuáveis

O comportamento prefere tópicos, no escopo da própria conversa da tarefa:

  • Plataformas com tópico (tópicos do Telegram, threads do Discord e do Slack): cada entrega abre o próprio tópico, e o resumo é semeado naquela sessão, então uma resposta ali continua com contexto completo. Uma tarefa recorrente (um resumo diário, por exemplo) abre um tópico novo por execução, mantendo cada discussão isolada.
  • Plataformas só de mensagem direta (WhatsApp, Signal, SMS): não existem tópicos, então o resumo é espelhado na sessão da conversa de origem — a própria conversa é a superfície de continuação.

Só a conversa da própria tarefa é tocada:

  • a conversa de origem em que ela foi criada;
  • o canal principal como reserva, quando o deliver: origin não capturou origem nenhuma (tarefas criadas por script ou pela API, e não numa conversa viva de gateway) — a conversa principal da pessoa fazendo as vezes da origem;
  • um destino explícito único de plataforma:conversa, mas só quando a própria tarefa opta com attach_to_session: true — quem escreveu a tarefa declara aquele destino uma conversa. O mirror_delivery global sozinho nunca torna continuável uma conversa endereçada explicitamente.

Destinos de difusão (all, canais principais sem endereço) nunca viram continuáveis. O espelho é escrito como um turno de usuário rotulado ([Cron delivery: <nome da tarefa>]), o que mantém a alternância do histórico segura em todos os provedores de modelo.

Continuação plana, no canal (Slack)​

O comportamento acima cria um tópico a cada entrega. Se você prefere que uma tarefa continuável caia plana na linha do tempo do canal, sem tópico, defina a superfície de continuação do Slack como in_channel:

# ~/.hermes/config.yaml
slack:
cron_continuable_surface: in_channel # padrão: thread
reply_in_thread: false # emparelhamento obrigatório (veja abaixo)
require_mention: false # para uma resposta simples continuar a tarefa

No modo in_channel, o resumo é entregue como mensagem comum de topo (sem abrir tópico), e a sua resposta continua a tarefa pela sessão compartilhada do canal. Três opções trabalham juntas:

  • cron_continuable_surface: in_channel — pula a criação de tópico na entrega.
  • reply_in_thread: false (obrigatória) — faz o bot responder plano no canal, na mesma sessão do canal inteiro em que o resumo foi semeado. Sem ela, a continuação ainda funciona, mas chega num tópico (ela recua com segurança para a continuação em tópico, nunca perde a resposta — e o gateway registra um aviso na inicialização, para você notar a incoerência).
  • require_mention: false (ou acrescente o canal a free_response_channels) — para você responder com uma mensagem comum; senão o bot só acorda quando é mencionado a cada resposta.

Como a continuação é a sessão do canal inteiro, ela é compartilhada: outras conversas no canal — e uma segunda tarefa continuável no mesmo modo — entram na mesma conversa rolante. Isso é inerente ao "plano num canal", e é o mesmo custo que quem usa reply_in_thread: false já aceita; use a superfície padrão, thread, quando quiser cada continuação isolada.

Hoje é uma capacidade do Slack. As outras plataformas aceitam a chave, mas recuam para thread (as primitivas de continuação delas são diferentes); a escolha é por plataforma, na configuração de cada uma. É uma opção do lado do gateway — um /restart já pega; não é preciso reinstalar o aplicativo no Slack.

Conversas individuais

O cron_continuable_surface é uma configuração de canal — uma conversa individual não tem divisão entre tópico e linha do tempo (ela já é plana), então a chave não tem efeito ali. Quem manda numa conversa individual é a opção separada slack.dm_top_level_threads_as_sessions:

  • false — todas as mensagens de topo dividem uma sessão rolante, então o resumo continuável e a sua resposta caem na mesma sessão, e a tarefa continua com contexto. É o que você quer para cron continuável em conversa individual.
  • true (padrão) — cada mensagem de topo é uma sessão própria, então responder a um resumo entregue começa uma sessão nova, sem registro dele. A continuação não funciona nesse modo (nem para cron, nem para qualquer outra entrega plana).

Então, para uma tarefa continuável entregue numa conversa individual, defina slack.dm_top_level_threads_as_sessions: false. O cron_continuable_surface não é necessário (e é ignorado) em conversas individuais.

Supressão silenciosa​

Se a resposta final do agente contém [SILENT], a entrega é suprimida por completo. A saída ainda é salva localmente para auditoria (em ~/.hermes/cron/output/), mas nenhuma mensagem é enviada.

Serve para tarefas de monitoramento, que só devem falar quando algo está errado:

Veja se o nginx está rodando. Se estiver tudo bem, responda só [SILENT].
Se não, relate o problema.

Tarefas que falharam sempre entregam, independentemente do [SILENT] — só execuções bem-sucedidas podem ser silenciadas.

Tempo limite de script​

Scripts de pré-execução (anexados pelo parâmetro script) têm tempo limite padrão de 3600 segundos (1 hora). Isso limita só o script — tarefas com skill ou com modelo rodam num orçamento de inatividade separado, e não são limitadas por esse valor. Para mudar:

# ~/.hermes/config.yaml
cron:
script_timeout_seconds: 1800 # 30 minutos

Ou defina a variável HERMES_CRON_SCRIPT_TIMEOUT. A ordem de resolução é: variável de ambiente → config.yaml → 3600 segundos.

O cron também limita a limpeza de sessão e de recursos depois da execução. Isso acontece depois de a chamada do modelo retornar, então é separado do tempo de inatividade. O padrão é 10 segundos por operação de limpeza. Se um finalizador de armazenamento ou de cliente parar de responder, o agendador registra um erro, libera a trava da tarefa e permite que execuções futuras aconteçam, em vez de pular aquela tarefa para sempre.

# ~/.hermes/config.yaml
cron:
cleanup_timeout_seconds: 10

Coloque cleanup_timeout_seconds: 0 só para voltar ao comportamento antigo, sem limite.

Tempo limite de envio de mídia​

Quando uma entrega inclui anexos (um PDF gerado, um áudio, um relatório exportado) enviados por um adaptador de gateway vivo, cada envio é limitado por um tempo — 300 segundos, por padrão. Arquivos grandes em conexões lentas podem precisar de mais:

# ~/.hermes/config.yaml
cron:
media_send_timeout_seconds: 600 # 10 minutos por anexo

Ou defina a variável HERMES_CRON_MEDIA_SEND_TIMEOUT. Um anexo que estourou o tempo é registrado no estado da execução como falha parcial de entrega (o texto ainda chega).

Tempo limite da entrega em conversa de bot​

Uma entrega bot-chat roda um turno completo de agente na conversa do bot de destino, então o limite é de minutos, não de segundos — 600 por padrão:

# ~/.hermes/config.yaml
cron:
bot_chat_delivery_timeout_seconds: 900

Uma entrega que estoura o tempo é registrada em last_delivery_error; o turno do bot pode terminar sozinho mesmo assim.

Modo sem agente (tarefas só de script)​

Para tarefas recorrentes que não precisam de raciocínio — vigias clássicos, alertas de disco e memória, batimentos, avisos de CI — passe no_agent=True na criação. O agendador roda o seu script no horário e entrega a saída padrão dele diretamente, pulando o agente:

hermes cron create "every 5m" \
--no-agent \
--script memory-watchdog.sh \
--deliver telegram \
--name "memory-watchdog"

Semântica:

  • Saída padrão do script (aparada) → entregue literal, como a mensagem.
  • Saída vazia → tique silencioso, sem entrega. É o padrão do vigia: "só fale quando algo estiver errado".
  • Saída diferente de zero, ou tempo esgotado → um alerta de erro é entregue, para um vigia quebrado não falhar em silêncio.
  • {"wakeAgent": false} na última linha → tique silencioso (a mesma trava das tarefas com modelo).
  • Sem token, sem modelo, sem reserva de provedor — a tarefa nunca toca a camada de inferência.

Arquivos .sh e .bash rodam sob o bash do PATH, quando existe, ou sob /bin/bash (importante no Git Bash do Windows). O resto roda sob o interpretador Python atual (sys.executable). Os scripts precisam resolver dentro de $HERMES_HOME/scripts/ — nomes relativos, caminhos absolutos e caminhos com ~ são aceitos quando o alvo resolvido continua nesse diretório; caminhos que escapam são recusados. O ambiente do subprocesso é higienizado: credenciais de provedor e outros segredos gerenciados pelo Hermes não são herdados por scripts de cron.

O agente monta isso para você​

O esquema da ferramenta cronjob expõe o no_agent ao Hermes diretamente, então você descreve um vigia na conversa e deixa o agente montar:

Me avise no Telegram se a RAM passar de 85%, a cada 5 minutos.

O Hermes escreve o script em ~/.hermes/scripts/ com o write_file e então chama:

cronjob(action="create", schedule="every 5m",
script="memory-watchdog.sh", no_agent=True,
deliver="telegram", name="memory-watchdog")

Ele escolhe no_agent=True sozinho quando o conteúdo da mensagem é totalmente determinado pelo script (vigias, alertas de limiar, batimentos). A mesma ferramenta também deixa o agente pausar, retomar, editar e remover — então o ciclo inteiro é conduzido pela conversa, sem ninguém tocar a CLI.

Veja o guia de cron só com script para exemplos completos.

Encadear tarefas com context_from​

Tarefas de cron rodam em sessões isoladas, sem memória das execuções anteriores. Mas às vezes a saída de uma é exatamente o que a próxima precisa. O parâmetro context_from faz essa ligação sozinho — o prompt da tarefa B recebe a saída mais recente da tarefa A como contexto, em tempo de execução.

# Tarefa 1: coleta os dados crus
cronjob(
action="create",
prompt="Fetch the top 10 AI/ML stories from Hacker News. Save them to ~/.hermes/data/briefs/raw.md in markdown format with title, URL, and score.",
schedule="0 7 * * *",
name="AI News Collector",
)

# Tarefa 2: tria — recebe a saída da tarefa 1 como contexto
# Pegue o identificador da tarefa 1 com: cronjob(action="list")
cronjob(
action="create",
prompt="Read ~/.hermes/data/briefs/raw.md. Score each story 1–10 for engagement potential and novelty. Output the top 5 to ~/.hermes/data/briefs/ranked.md.",
schedule="30 7 * * *",
context_from="<id_da_tarefa1>",
name="AI News Triage",
)

# Tarefa 3: entrega — recebe a saída da tarefa 2 como contexto
cronjob(
action="create",
prompt="Read ~/.hermes/data/briefs/ranked.md. Write 3 tweet drafts (hook + body + hashtags). Deliver to telegram:7976161601.",
schedule="0 8 * * *",
context_from="<id_da_tarefa2>",
name="AI News Brief",
)

Como funciona:

  • Quando a tarefa 2 dispara, o Hermes lê a saída mais recente da tarefa 1, em ~/.hermes/cron/output/{id_tarefa1}/*.md
  • Essa saída é colocada antes do prompt da tarefa 2, automaticamente
  • A tarefa 2 não precisa dizer "leia este arquivo" — ela recebe o conteúdo como contexto
  • A corrente pode ter qualquer tamanho: tarefa 1 → tarefa 2 → tarefa 3 → …

O que o context_from aceita:

FormatoExemplo
Um identificador (texto)context_from="a1b2c3d4"
Vários identificadores (lista)context_from=["job_a", "job_b"]

As saídas são concatenadas na ordem da lista.

Continuidade: carregar a saída da execução anterior

Coloque continuity=true e a tarefa injeta a própria saída mais recente em cada execução. Tarefas recorrentes normalmente começam com amnésia — um vigia de notícias repete as mesmas histórias, um monitor realerta a mesma condição. Com continuidade, a tarefa acorda vendo o que relatou da última vez, e consegue não repetir e seguir de onde parou:

cronjob(
action="create",
prompt="Scan HN and arXiv for new agent-tooling papers. Report only items NOT already covered in your previous run's output.",
schedule="every 6h",
continuity=True,
name="Agent Tooling Scout",
)

A primeira execução não tem saída anterior, então o prompt roda como está. Nas seguintes, a saída anterior entra antes, com um enquadramento de continuidade ("evite repetir o que já foi relatado"). Combina livremente com tarefas anteriores (context_from=["<id_de_outra>"] mais continuity=true), e continuity=false na atualização desliga, preservando as outras entradas de context_from. Por dentro, o campo é gravado como a entrada reservada self, dentro de context_from.

Pela CLI: hermes cron create "every 6h" "Procure novidades" --continuity, e hermes cron edit <id> --continuity ou --no-continuity para ligar e desligar numa tarefa existente. O mesmo botão aparece no editor de cron do painel e no diálogo de rotinas do Bot Mode, no desktop.

Quando usar:

  • Pipelines de várias etapas (coletar → filtrar → formatar → entregar)
  • Tarefas dependentes, em que o passo N depende da saída do passo N−1
  • Padrões de espalhar e juntar, em que uma tarefa agrega o resultado de várias
  • Vigias e monitores recorrentes, que devem evitar repetir o próprio relatório anterior (continuity=true)

Recuperação de provedor​

Tarefas de cron herdam os seus provedores de reserva e a rotação de pool de credenciais. Se a chave principal atinge o limite de requisições, ou o provedor devolve erro, o agente de cron consegue:

  • cair num provedor alternativo, se você tem fallback_providers (ou o antigo fallback_model) configurado
  • girar para a credencial seguinte do seu pool, no mesmo provedor

Isso deixa tarefas de alta frequência, ou que rodam no horário de pico, mais resistentes — uma chave sozinha, limitada, não derruba a execução inteira.

Disparos agendados perdidos (last_fire_error)​

Em instalações hospedadas (com cron gerenciado), um disparo agendado viaja do agendador da plataforma até o painel, e daí ao servidor de API interno do gateway. Se esse último salto falha — o processo do gateway caiu, ou o servidor de API dele nunca subiu — a execução nem começa, então não há registro nem last_status para inspecionar. O sinal característico: a tarefa funciona toda vez que você dispara na mão, mas nunca dispara sozinha.

Essas perdas ficam carimbadas no registro da tarefa como last_fire_error (com carimbo de tempo e motivo), e aparecem:

  • na ferramenta cronjob, na ação list — no campo last_fire_error
  • no hermes cron list — numa linha vermelha, ⚠ Missed scheduled fire:, sob a tarefa
  • na visão de tarefa do painel

O carimbo sempre reflete a saúde atual do disparo automático: ele é sobrescrito por perdas mais novas e limpo sozinho pela próxima execução bem-sucedida. Se você o vê, a tarefa e o agendamento estão bem — é o lado do gateway que precisa de atenção (mais comum: reinicie o gateway pelo supervisor, para ele carregar o ambiente completo do perfil, com hermes gateway restart).

Recuperação de disparo perdido​

Quando um agendador externo está ativo (cron gerenciado em instalações hospedadas), o gateway também roda uma varredura de recuperação: uma tarefa cujo horário passou sem disparo — e cuja janela de tolerância já acabou — é reivindicada e rodada localmente, então uma queda no caminho do disparo custa minutos, e não o dia inteiro. A varredura não duplica com as tentativas atrasadas do agendador, por usar a mesma reivindicação de armazenamento dos disparos normais.

cron:
misfire_grace_minutes: 10 # espera este tempo pelas tentativas do próprio agendador
# antes de recuperar localmente; 0 desliga

Instalações locais, com o tique embutido, não precisam disso — o tique já pega as tarefas atrasadas no próximo ciclo.

Formatos de agendamento​

Atrasos relativos (execução única)​

30m → roda uma vez, em 30 minutos
2h → roda uma vez, em 2 horas
1d → roda uma vez, em 1 dia

Intervalos (recorrente)​

every 30m → a cada 30 minutos
every 2h → a cada 2 horas
every 1d → todo dia

Expressões cron​

0 9 * * * → todo dia às 9h
0 9 * * 1-5 → dias úteis às 9h
0 */6 * * * → a cada 6 horas
30 8 1 * * → todo dia 1º, às 8h30
0 0 * * 0 → todo domingo, à meia-noite

Carimbos ISO​

2026-03-15T09:00:00 → uma vez, em 15 de março de 2026, às 9h

Repetição​

Tipo de agendamentoRepetição padrãoComportamento
Única (30m, carimbo)1Roda uma vez
Intervalo (every 2h)para sempreRoda até ser removida
Expressão cronpara sempreRoda até ser removida

Dá para sobrescrever:

cronjob(
action="create",
prompt="...",
schedule="every 2h",
repeat=5,
)

Gerenciar por programa​

A API que o agente vê é uma ferramenta só:

cronjob(action="create", ...)
cronjob(action="list")
cronjob(action="update", job_id="...")
cronjob(action="pause", job_id="...")
cronjob(action="resume", job_id="...")
cronjob(action="run", job_id="...")
cronjob(action="remove", job_id="...")

No update, passe skills=[] para remover todas as skills anexadas.

Execuções manuais são assíncronas​

O cronjob(action="run") dispara a tarefa na hora, em segundo plano (como o delegate_task): a chamada volta na mesma hora, com um identificador, e o desfecho — sucesso ou falha, destino de entrega, próxima execução agendada e um trecho da saída — reentra na conversa como mensagem nova quando a execução termina. O agente (e você) segue trabalhando enquanto isso, e uma tarefa que já está no meio de uma execução é recusada com "already running", em vez de disparar duas vezes.

Também dá para passar um prompt junto do action="run", para injetar contexto naquela execução:

cronjob(action="run", job_id="...", prompt="CONTEXT: focus on the EU region today")

O contexto é acrescentado ao prompt gravado, sob um cabeçalho ## Run Context, só naquele disparo — ele nunca é gravado na definição da tarefa, e passa pela mesma varredura de injeção de prompt.

Ambientes que não recebem resultados desanexados (o hermes -z de uma vez só, o hermes cron run da CLI, sessões filhas de cron, trabalhadores do kanban) caem em execução síncrona automaticamente.

Ferramentas disponíveis às tarefas de cron​

O cron roda cada tarefa numa sessão nova, sem plataforma de conversa. Por padrão, o agente de cron recebe o conjunto de ferramentas que você configurou para a plataforma cron, em hermes tools — não o padrão da CLI, nem tudo que existe.

hermes tools
# → escolha a plataforma "cron" na interface
# → ligue e desligue conjuntos, como faria para Telegram, Discord e o resto

Há controle mais fino por tarefa, no campo enabled_toolsets do cronjob.create (ou do cronjob.update, numa tarefa existente):

cronjob(action="create", name="weekly-news-summary",
schedule="every sunday 9am",
enabled_toolsets=["web", "file"], # só web e arquivo, sem terminal, navegador etc.
prompt="Summarize this week's AI news: ...")

Quando o enabled_toolsets está definido numa tarefa, ele vence; senão vale a configuração da plataforma cron do hermes tools; senão, os padrões embutidos. Isso importa para controlar custo: carregar browser e delegation em toda tarefinha de "buscar notícias" incha o esquema de ferramentas em cada chamada de modelo.

Pular o agente por completo: wakeAgent​

Se a sua tarefa anexa um script de pré-checagem (por script=), ele decide em tempo de execução se o Hermes deve sequer chamar o agente. Emita uma última linha na saída padrão assim:

{"wakeAgent": false}

…e o cron pula a execução do agente naquele tique. Útil em consultas frequentes (a cada 1 a 5 minutos), que só precisam acordar o modelo quando o estado mudou de verdade — senão você paga por turnos vazios, repetidamente.

# script de pré-checagem
import json, sys
latest = fetch_latest_issue_count()
prev = read_state("issue_count")
if latest == prev:
print(json.dumps({"wakeAgent": False})) # pula este tique
sys.exit(0)
write_state("issue_count", latest)
print(json.dumps({"wakeAgent": True, "context": {"new_issues": latest - prev}}))

Sem o wakeAgent, o padrão é true (acordar o agente).

Receitas: travas baratas antes de rodar​

A trava de wakeAgent dá um jeito de custo zero de decidir se uma tarefa agendada deve gastar qualquer token. Três padrões cobrem quase tudo.

Trava por mudança de arquivo — só roda quando um arquivo observado tem conteúdo novo desde o último tique bem-sucedido. O agendador registra o last_run_at de cada tarefa; compare com a data de modificação do arquivo.

#!/bin/bash
# ~/.hermes/scripts/feed-changed.sh
FEED="$HOME/data/feed.json"
STATE="$HOME/.hermes/scripts/.feed-changed.last"
test -f "$FEED" || { echo '{"wakeAgent": false}'; exit 0; }
mtime=$(stat -c %Y "$FEED")
last=$(cat "$STATE" 2>/dev/null || echo 0)
if [ "$mtime" -le "$last" ]; then
echo '{"wakeAgent": false}'
else
echo "$mtime" > "$STATE"
echo '{"wakeAgent": true}'
fi
cronjob(action="create", name="process-feed",
schedule="every 30m",
script="feed-changed.sh",
prompt="A new ~/data/feed.json has landed. Summarize what changed.")

Trava por sinalizador externo — só roda quando outro processo sinalizou que está pronto (um gancho de publicação deixa um arquivo, uma tarefa de CI grava um valor).

#!/bin/bash
# ~/.hermes/scripts/flag-ready.sh
if test -f /tmp/new-data-ready; then
rm -f /tmp/new-data-ready
echo '{"wakeAgent": true}'
else
echo '{"wakeAgent": false}'
fi
cronjob(action="create", name="nightly-analysis",
schedule="0 9 * * *",
script="flag-ready.sh",
prompt="Run the nightly analysis over today's batch.")

Trava por contagem em SQL — só roda quando há linhas novas a processar no seu banco. O script também passa a contagem ao agente, pelo context, então ele sabe o volume sem consultar de novo.

#!/usr/bin/env python
# ~/.hermes/scripts/new-rows.py
import json, sqlite3
conn = sqlite3.connect("/home/eu/dados/app.db")
n = conn.execute(
"SELECT COUNT(*) FROM messages WHERE ts > strftime('%s','now','-2 hours')"
).fetchone()[0]
if n < 1:
print(json.dumps({"wakeAgent": False}))
else:
print(json.dumps({"wakeAgent": True, "context": {"new_rows": n}}))
cronjob(action="create", name="summarize-new-msgs",
schedule="every 2h",
script="new-rows.py",
prompt="Summarize the new messages from the last 2 hours.")

O mesmo padrão vale para qualquer fonte de dados que você consulte de um script — Postgres, uma API HTTP, o seu próprio armazenamento de estado — sem embutir um avaliador de SQL no subsistema de cron.

dica

O ~/.hermes/state.db do próprio Hermes é um esquema interno, que muda entre versões. Não consulte ele numa trava de pré-execução — aponte para o seu próprio banco ou feed.

Crédito: este conjunto de receitas nasceu da exploração de @iankar8 no #2654, que propunha acrescentar gatilhos de SQL, arquivo e comando como mecanismo paralelo. A trava de script mais wakeAgent já cobre os três casos a custo zero, então o trabalho virou documentação.

Encadear tarefas: context_from​

Uma tarefa de cron consome a saída bem-sucedida mais recente de uma ou mais outras, listando os nomes (ou identificadores) delas em context_from:

cronjob(action="create", name="daily-digest",
schedule="every day 7am",
context_from=["ai-news-fetch", "github-prs-fetch"],
prompt="Write the daily digest using the outputs above.")

As saídas concluídas mais recentes das tarefas citadas entram acima do prompt, como contexto daquela execução. Cada entrada precisa ser um identificador ou nome válido (veja cronjob action="list"). Atenção: o encadeamento lê a saída concluída mais recente — ele não espera tarefas anteriores que estejam rodando no mesmo tique.

Onde as tarefas ficam​

As tarefas ficam em ~/.hermes/cron/jobs.json. A saída das execuções vai para ~/.hermes/cron/output/{id_da_tarefa}/{carimbo}.md.

As definições são JSON puro no disco: elas sobrevivem ao hermes update, a reinícios do gateway e a reinícios da máquina. Uma tarefa que estava no meio de uma execução durante um reinício é marcada como unknown no registro — ela não é repetida sozinha, mas o próximo tique agendado dispara normalmente. Veja histórico de execução.

dica

Peça ao agente para gerenciar as tarefas pela ferramenta cronjob, pelo hermes cron edit ou pelo /cron — e não editando o jobs.json direto. Edições diretas podem falhar em silêncio quando a proteção de escrita bloqueia o caminho (quando o HERMES_WRITE_SAFE_ROOT está definido, por exemplo), e o rodapé do verificador de mudanças é o sinal autoritativo de que nada foi salvo.

As tarefas podem guardar model e provider como null. Quando esses campos estão ausentes, o Hermes os resolve na hora da execução, pela configuração global. Eles só aparecem no registro quando há uma sobrescrita por tarefa.

O armazenamento usa escrita atômica de arquivo, então uma escrita interrompida não deixa um arquivo de tarefa pela metade.

Prompts autossuficientes continuam importando​

Importante

Tarefas de cron rodam numa sessão de agente completamente nova. O prompt precisa conter tudo de que o agente precisa e que não venha das skills anexadas.

RUIM: "Confira aquele problema do servidor"

BOM: "Conecte por SSH no servidor 192.168.1.100 como usuário 'deploy', veja se o nginx está rodando com 'systemctl status nginx', e confirme que https://exemplo.com devolve HTTP 200."

Segurança​

Os prompts das tarefas agendadas são varridos atrás de padrões de injeção de prompt e de exfiltração de credencial, na criação e na atualização. Prompts com truques de Unicode invisível, tentativas de backdoor em SSH, ou cargas óbvias de vazamento de segredo são bloqueados.