Automação · agentes de código
O scheduler que mora dentro do seu agente
O Command Code tem um agendador de tarefas embutido: lembretes únicos, tarefas recorrentes no cron e loops que se ajustam sozinhos. Sem cron do sistema, sem serviço externo — o job acorda o agente, e o agente faz o trabalho. Este estudo mostra como tudo funciona por dentro.
*/5minuto*hora*dia do mês*mês*dia da semana*/5 * * * * — lê-se: a cada 5 minutos, de toda hora, todo dia, todo mês, toda semana. É a expressão do job real que aparece no capítulo 06.
O que é o scheduler do Command Code
É um agendador de tarefas que vive dentro do agente. Você descreve o que quer que aconteça — em linguagem natural ou com uma expressão cron — e o Command Code registra uma tarefa. Quando o horário chega, o scheduler injeta aquela instrução como um novo turno do agente: ele executa o trabalho e ainda te dá uma resposta no chat.
A diferença para um cron tradicional é o que roda no horário marcado. Não é um script fixo: é o próprio agente, com todas as ferramentas dele — shell, arquivos, web, WhatsApp via MCP, sub-agentes. E a diferença para um serviço externo é onde ele mora: na sua conversa.
- O que dispara
- Um turno do agente com o prompt que você definiu — como se você tivesse digitado aquilo na hora.
- Quem pode criar
- Você, pedindo em linguagem natural, ou o agente via
cron_create. Fixos também via/loop. - Onde vive
- Na conversa (padrão). Ou em
~/.commandcode/cron/jobs.jsonquando criado como durable. - Quanto dura
- Tarefas recorrentes expiram em 7 dias. One-shots se deletam depois de disparar.
Anatomia de um job: o cron_create
Todo job nasce de uma ferramenta só. Ela aceita quatro campos — e cada um responde uma pergunta diferente.
cron obrigatório
Quando? Expressão de 5 campos em horário local: minuto hora dia-do-mês mês dia-da-semana.
Aceita *, valores, passos (*/5), faixas (1-5) e listas (1,15). Sem aliases como @daily. Quando dia-do-mês e dia-da-semana estão ambos restritos, qualquer um dos dois pode casar.
prompt obrigatório
O quê? A instrução que vira um turno do agente a cada disparo.
Escreva auto-contido: o job acorda sem o contexto da conversa. Dados, caminhos e destinos precisam estar no texto.
recurring opcional
Repete? true dispara em cada casamento do cron; false é one-shot — dispara uma vez e se deleta sozinho.
durable opcional
Sobrevive à conversa? true grava o job em ~/.commandcode/cron/jobs.json, fora da sessão. Use só quando precisar de agendamento de máquina de verdade.
cron_create(
cron: "*/5 * * * *", // a cada 5 minutos
prompt: "Busca o resultado, resume e envia no WhatsApp.",
recurring: true,
durable: false // vive nesta conversa
)
Umas expressões para guardar de referência:
| Expressão | O que faz | Exemplo de uso |
|---|---|---|
*/5 * * * * | A cada 5 minutos | ticker de apuração, monitoramento |
7 11 * * 1-5 | 11h07, de segunda a sexta | resumo matinal antes do almoço |
20 16 4 10 * | 04/10 às 16h20 — uma vez só | lembrete datado (one-shot) |
6 9 1 * * | Dia 1º de cada mês, 09h06 | relatório mensal |
Por que minutos estranhos (06, 07, 53)? Dois motivos. O scheduler adiciona jitter pra espalhar carga, então horários “redondos” não são garantia de exatidão. E one-shots marcados em :00 ou :30 podem disparar até 90 segundos antes. Quando o timing exato importar, escolha outro minuto.
Como um job dispara
O caminho entre “chegou a hora” e “o agente fez o trabalho” tem cinco estágios — e cada um tem uma decisão de engenharia por trás.
-
01
O scheduler checa a cada segundo. Um relógio interno varre as tarefas da conversa continuamente — não é um processo externo.
-
02
Só submete entre turnos. Se o agente está ocupado respondendo algo, o evento espera o turno terminar. Nunca há duas coisas rodando em cima uma da outra.
-
03
Sem rajada de recuperação. Ficou 1 hora offline com um job de 5 em 5 minutos? Ao voltar, ele dispara uma vez — não 12. Intervalos perdidos não geram catch-up em massa.
-
04
Jitter determinístico. Tarefas recorrentes podem disparar até 10% do intervalo mais tarde (cap de 15 minutos). O atraso vem do id da tarefa, então é sempre o mesmo — inclusive depois de retomar a conversa.
-
05
O texto chega cercado como dado. A instrução agendada entra em cena identificada como task data — ela não pode se passar por uma mensagem sua nem ampliar permissões.
Ida e volta na prática: com um job de 5 minutos, espere o disparo na janela de 5min + até ~30s de jitter. Não é “no segundo exato” — e isso é de propósito.
Ciclo de vida e escopo
Gerenciar é simples: um comando pra criar, um pra listar, um pra cancelar. O que exige atenção é onde a tarefa vive — porque isso decide se ela sobrevive a um restart, a um /clear ou a um fim de semana.
cron_create // cria (recorrente ou one-shot)
cron_list // lista tudo, com ids de 8 caracteres
cron_delete // cancela pelo id
// na conversa, você também pode simplesmente pedir:
"quais tarefas agendadas eu tenho?"
"cancela a checagem do deploy"
Escopo conversa default
Vive nesta conversa. Fechar o Command Code pausa os disparos; --resume ou --continue restaura recorrentes ainda dentro dos 7 dias e one-shots cujo horário ainda não passou.
Conversa nova não roda tarefas de outra. Uma conversa pode ter até 50 tarefas agendadas.
Durable opt-in
Vive na máquina. Gravada em ~/.commandcode/cron/jobs.json, independente de retomar conversa. Um lease evita que duas sessões abertas disparem a mesma tarefa.
One-shot durable perdido com o app fechado pede confirmação no próximo launch.
Expiração: toda tarefa recorrente morre 7 dias depois de nascer. Uma tarefa fixa em atividade ganha um último disparo no vencimento; um loop self-paced simplesmente para no limite. Precisa de algo perpétuo? Recrie antes de expirar ou use um scheduler externo chamando o modo headless.
COMMANDCODE_DISABLE_CRON=1 // desliga /loop e todas as ferramentas do scheduler
COMMANDCODE_DISABLE_DURABLE_CRON=1 // rebaixa durable → escopo de conversa
Detalhe honesto: cada disparo roda um turno normal do agente. Um job de 5 minutos gera até 288 turnos por dia — vale dimensionar a frequência como você dimensionaria qualquer automação.
Loops: fixos e self-paced
Para o caso “roda agora e fica de olho”, existe o /loop — um atalho de primeira classe para o scheduler, com parsing determinístico de cadência (você não depende do modelo adivinhar o intervalo).
/loop 5m check the deploy // roda agora + fixo a cada 5 min
/loop check the deploy // roda agora + o agente escolhe o próximo intervalo
/loop 15m // tarefa padrão a cada 15 min
/loop // tarefa padrão com ritmo adaptativo
Fixo — ticker previsível
Você dá o intervalo (s, m, h, d) e a primeira execução é imediata. Cadências que não encaixam num passo cron limpo são normalizadas para a mais próxima — e a confirmação mostra qual foi escolhida.
Recebe o jitter determinístico dos recorrentes (até 10% do intervalo).
Self-paced — ritmo adaptativo
Sem intervalo, o agente decide o próximo passo a cada iteração: de 1 minuto a 1 hora, com uma razão curta registrada. Cada escolha substitui a anterior — não empilha.
Sem jitter. Um por conversa. Se uma iteração terminar sem decidir o próximo passo, rola um check de segurança em 20 minutos; se ele também não decidir, o loop encerra.
Tarefa padrão: um /loop sem tarefa usa uma manutenção pré-definida (continuar trabalho inacabado, cuidar de PR/CI da branch atual, uma passada de qualidade). Você pode trocar por um arquivo markdown: .commandcode/loop.md tem precedência; ~/.commandcode/loop.md é o fallback. O arquivo é relido a cada iteração e truncado se passar de 25 kB.
Onde não roda: modo headless (cmd -p), dentro de sub-agentes e em plan mode — ali, agendar é uma escrita, e escrita está bloqueada. Para parar: Esc cancela os loops pendentes da conversa sem tocar nos seus outros agendamentos.
O caso real: apuração → WhatsApp
Este estudo nasceu de um job de verdade. O pedido: “manda a apuração da eleição pra Edna no WhatsApp a cada 5 minutos”. O que foi montado:
Envie a apuração ao vivo das eleições para Edna (chatId 5521…@c.us,
sessão WAHA "default").
1) Busque os dados oficiais do TSE via curl (com cache-buster).
Se der 404, tente o código do 2º turno. Se ambos falharem, não envie.
2) Extraia: % de seções apuradas, comparecimento e os 2 candidatos
mais votados — com vantagem em pontos e em votos.
3) Envie via mcp__waha__send-text no formato definido aqui.
Envie em toda execução, mesmo sem mudança nos números.
4) Responda no chat apenas com uma linha de confirmação.
A cada 5 minutos, o scheduler acorda o agente; o agente faz o curl, formata e dispara a mensagem. Três lições que esse job ensina:
- O prompt é auto-contido. Nada de “como combinamos antes” — o job carrega o comando do TSE, o chatId e o formato da mensagem dentro dele.
- Falha tem caminho de saída. O prompt define o fallback (2º turno) e a regra de silêncio (se falhar, não manda nada) — sem isso, um 404 viraria mensagem de erro pro destinatário.
- Escopo importa. Como é escopo conversa, o envio continua enquanto a conversa for retomada dentro dos 7 dias. Se o app ficasse fechado, os disparos pausariam — e nada de catch-up ao voltar.
Boas práticas e pegadinhas
O que separa um job que funciona de um que vira ruído:
- Escreva o prompt pensando num estranho. Quem executa é o agente sem o contexto da conversa — inclua caminhos, ids, destinos e formato.
- Teste com one-shot antes. “Em 2 minutos, faça X” valida o fluxo inteiro sem risco de repetição. Depois promova para recorrente.
- Não conte com exatidão. Recorrentes têm até 10% de jitter; one-shot em :00/:30 pode adiantar até 90s. Para timing exato de verdade, use um scheduler externo.
- App fechado = tarefa pausada. No escopo conversa, nada dispara com o Command Code fechado. Precisa disso de pé?
durable: trueou cron do sistema. - Efeitos externos pedem cuidado. Um job que manda mensagem, abre PR ou publica algo repete o efeito a cada disparo. Defina idempotência no prompt (ex.: “só envie se o número mudou”).
- Lembre dos 7 dias. Recorrentes expiram. Para algo mais longo, planeje recriar — ou vá de scheduler externo + headless.
- Respeite o destinatário. Frequência de 5 minutos faz sentido numa apuração ao vivo; para status de rotina, 1–2 vezes por dia costuma bastar.
- Cada disparo custa um turno. É o comportamento desejado, mas vale lembrar: alta frequência = muitos turnos de agente por dia.
Quando usar o quê
| Você quer… | Use | Por quê |
|---|---|---|
| Um lembrete único (“em 45 min…”, “às 15h05…”) | cron_create one-shot | dispara uma vez e se deleta sozinho |
| Rodar agora e re-checar num ritmo fixo | /loop 5m … | primeira execução imediata + cadência previsível |
| Re-checar com ritmo adaptativo | /loop … | o agente decide o próximo intervalo (1 min–1 h) |
| Sobreviver a restart, sem conversa | durable: true | grava em ~/.commandcode/cron/jobs.json |
| Rodar para sempre / em headless | Scheduler externo + cmd -p | recorrentes internas expiram em 7 dias |
| Esperar algo terminar (interrompível) | sleep | pausa sem segurar processo; acorda se você digitar |
Regra de bolso: precisa de contexto entre execuções, ou de efeito externo? Trate como automação de verdade — prompt auto-contido, idempotência e uma válvula de parada. É só um lembrete? Linguagem natural basta: “me lembra às 15h05 de revisar o PR”.